From 8dd3df42aacfe54f589fc84f3fd8ed00ca663703 Mon Sep 17 00:00:00 2001 From: Lucietta De Staccato Date: Thu, 22 May 2025 14:36:41 -0400 Subject: [PATCH 01/25] patch loop --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 4 ++++ 1 file changed, 4 insertions(+) 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..114a064ce 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 @@ -162,6 +162,7 @@ module ol_framework::page_rank_lazy { // Calculate power passed to neighbors (50% decay) + // This division represents the power reduction for being one hop away from the current node let next_power = current_power / 2; // if the both current and target are a root of trust @@ -201,12 +202,15 @@ module ol_framework::page_rank_lazy { if( root_of_trust::is_root_at_registry(@diem_framework, neighbor) ) { + i = i + 1; continue }; }; // Continue search from this neighbor with reduced power + // We pass the same next_power to each neighbor at the same level + // to ensure power reduction happens per hop, not per neighbor let path_score = walk_from_node( neighbor, target, From 718426938833c425253940b8191620a9728dab4a Mon Sep 17 00:00:00 2001 From: Archibald Fitz Hind Date: Thu, 22 May 2025 15:02:56 -0400 Subject: [PATCH 02/25] wip tests --- .../ol_sources/tests/boundary.test.move | 103 ------------- .../ol_sources/tests/donor_voice.test.move | 1 + .../ol_sources/tests/page_rank.test.move | 139 +++++++++++++++++- .../tests/vote_lib/multi_action.test.move | 83 +++++------ .../ol_sources/vouch_lib/page_rank_lazy.move | 6 - .../sources/ol_sources/vouch_lib/vouch.move | 3 - 6 files changed, 176 insertions(+), 159 deletions(-) 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..2434ff4dc 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 @@ -458,6 +458,74 @@ 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)] /// When a vouch branch occurs, @@ -560,11 +628,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); + } - diem_std::debug::print(&7777777777777777777); - page_rank_lazy::refresh_cache(root1_addr); - // diem_std::debug::print(&root1_score_post); + #[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); + + let root0 = vector::borrow(&roots_sig, 0); + vouch::init(root0); + let root0_addr = signer::address_of(root0); + + 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/page_rank_lazy.move b/framework/libra-framework/sources/ol_sources/vouch_lib/page_rank_lazy.move index 114a064ce..9897fda1d 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 @@ -76,12 +76,8 @@ module ol_framework::page_rank_lazy { // 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); - // Update the cache let user_record_mut = borrow_global_mut(addr); user_record_mut.cached_score = score; @@ -183,8 +179,6 @@ module ol_framework::page_rank_lazy { while (i < neighbor_count) { let neighbor = *vector::borrow(&neighbors, i); - diem_std::debug::print(&neighbor); - // Only visit if not already in path (avoid cycles) if (!vector::contains(visited, &neighbor)) { 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? From cfdc8164924cf8e0ed1c1e2c35005ee28b8ab04b Mon Sep 17 00:00:00 2001 From: Bea O'Tempo Date: Thu, 22 May 2025 17:00:03 -0400 Subject: [PATCH 03/25] search all unique paths to an account --- .../ol_sources/tests/page_rank.test.move | 16 ++- .../ol_sources/vouch_lib/page_rank_lazy.move | 135 +++++++++++++----- 2 files changed, 107 insertions(+), 44 deletions(-) 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 2434ff4dc..4aeb67b47 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 @@ -531,22 +531,27 @@ module ol_framework::test_page_rank { /// When a vouch branch occurs, /// and then merges back to a user, /// the transitive scores should accumulate + // Root0 -> Alice + // Root1 -> Alice + // Root0 -> Bob -> Alice fun diamond_pattern_accumulates(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); - let root0 = vector::borrow(&roots_sig, 0); - vouch::init(root0); + // Both roots vouch for alice vouch_txs::vouch_for(root0, alice_addr); - - let root1 = vector::borrow(&roots_sig, 1); - vouch::init(root1); vouch_txs::vouch_for(root1, alice_addr); let (received, _) = vouch::get_received_vouches(alice_addr); @@ -573,6 +578,7 @@ 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); 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 9897fda1d..0ad050996 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 @@ -23,25 +23,29 @@ module ol_framework::page_rank_lazy { const EMAX_PROCESSED_ADDRESSES: u64 = 3; //////// CONSTANTS //////// - // Max score on a single vouch + /// Maximum score that can be assigned to a single vouch. + /// This provides an upper bound on direct trust from a single source. const MAX_VOUCH_SCORE: u64 = 100_000; - // Circuit breaker to prevent stack overflow + /// Circuit breaker to prevent stack overflow during recursive graph traversal. + /// Limits the total number of nodes processed in a single traversal. const MAX_PROCESSED_ADDRESSES: u64 = 1_000; - // Per-user trust record - each user stores their own trust data + /// Per-user trust record - each user stores their own trust data + /// This resource tracks a user's cached trust score and staleness state. struct UserTrustRecord has key, drop { - // No need to store active_vouches - we'll get this from vouch module - // Cached trust score + /// Cached trust score - computed by traversing the trust graph cached_score: u64, - // When the score was last computed (timestamp) + /// When the score was last computed (timestamp in seconds) score_computed_at_timestamp: u64, - // Whether this node's trust data is stale and needs recalculation + /// Whether this node's trust data is stale and needs recalculation + /// Set to true when the trust graph changes in a way that affects this user is_stale: bool, // Shortest path to root now handled in a separate module } - // Initialize a user trust record if it doesn't exist + /// Initialize a user trust record if it doesn't 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 +58,18 @@ module ol_framework::page_rank_lazy { } - // Calculate or retrieve cached trust score + /// Calculate or retrieve cached trust score for an address. + /// Returns the cached score if it's valid, or recalculates if stale. + /// + /// 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,7 +84,13 @@ module ol_framework::page_rank_lazy { set_score(addr) } - // always calculate the score + /// Always calculate and update the trust score for an address. + /// This function: + /// 1. Gets the current roots of trust + /// 2. Traverses the graph to compute the score using our page rank algorithm + /// 3. Updates the user's cached score and marks it as fresh + /// + /// This is an expensive operation that should be used judiciously. 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)); @@ -87,7 +108,16 @@ module ol_framework::page_rank_lazy { score } - // Simplified graph traversal - only uses exhaustive walk + /// Simplified graph traversal that finds all valid paths from each root of trust to the target address. + /// This function iterates through each root in the provided list and accumulates scores from all + /// paths that lead to the target. + /// + /// For each root, it: + /// 1. Creates a new empty visited set to track paths independently + /// 2. Calculates the score contribution via walk_from_node, which explores all possible paths + /// 3. Adds the score to the total accumulation + /// + /// The total accumulated score represents the combined trust value from all roots to the target. fun traverse_graph( roots: &vector
, target: address, @@ -101,7 +131,8 @@ module ol_framework::page_rank_lazy { let root = *vector::borrow(roots, root_idx); let visited = vector::empty
(); - vector::push_back(&mut visited, root); + // Create an initial empty visited set for each root traversal + // No need to add root to visited at this level since walk_from_node handles it if (root != target) { // NOTE: root, you don't don't give yourself points in the walk @@ -119,7 +150,22 @@ module ol_framework::page_rank_lazy { total_score } - // Simplified full graph traversal from a single node - returns weighted score + /// Advanced graph traversal algorithm that finds and accumulates scores from all valid paths + /// from a starting node to a target. This function follows these principles: + /// + /// 1. Cycle Detection: Uses the visited set to avoid revisiting nodes already in the current path. + /// 2. Path Independence: Creates a copy of the visited set for each branch, ensuring separate paths + /// are explored independently. + /// 3. Score Accumulation: Accumulates scores from all valid and unique paths rather than only + /// returning the maximum score. This ensures "diamond patterns" (where multiple paths lead to + /// the same target) properly accumulate their trust contributions. + /// 4. Trust Decay: Implements a 50% power reduction per hop, representing diminishing trust + /// with distance from the source. + /// 5. Special Root Handling: Prevents accumulation from interconnected root accounts to avoid + /// artificial score inflation. + /// + /// The algorithm handles complex trust graphs including branching paths, merging paths + /// (diamond patterns), and multiple routes from roots to targets. fun walk_from_node( current: address, target: address, @@ -153,10 +199,9 @@ module ol_framework::page_rank_lazy { return 0 }; - // Track total score from all paths + // Track total accumulation of scores from all unique paths let total_score = 0; - // Calculate power passed to neighbors (50% decay) // This division represents the power reduction for being one hop away from the current node let next_power = current_power / 2; @@ -174,45 +219,41 @@ module ol_framework::page_rank_lazy { return next_power }; + // Add current to visited BEFORE checking neighbors + 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); - // Only visit if not already in path (avoid cycles) + // Only explore if not already in our path (avoid cycles) 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) - ) { - i = i + 1; - continue - }; + // Skip this path if the neighbor is a root of trust (other than target) + // as we don't want to accumulate points from roots vouching for each other + if ( + neighbor != target && + root_of_trust::is_root_at_registry(@diem_framework, neighbor) + ) { + i = i + 1; + continue }; + // Create a copy of visited for this path exploration + // This ensures each path can be explored independently + let path_visited = *visited; // Continue search from this neighbor with reduced power - // We pass the same next_power to each neighbor at the same level - // to ensure power reduction happens per hop, not per neighbor let path_score = walk_from_node( neighbor, target, - visited, + &mut path_visited, next_power ); - // Add to total score + // Add to total score (accumulate from all paths) + // This ensures that when multiple paths lead to the target ("diamond pattern"), + // all valid paths contribute to the final score rather than only taking the best one total_score = total_score + path_score; }; @@ -222,7 +263,11 @@ module ol_framework::page_rank_lazy { total_score } - // Mark a user's trust score as stale + /// Mark a user's trust score as stale, propagating the staleness to impacted downstream accounts. + /// This function performs a controlled graph traversal to identify all accounts that may + /// need to have their trust scores recalculated due to changes in the vouch graph. + /// + /// Uses cycle detection and a maximum node limit to prevent infinite recursion or DOS attacks. 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 @@ -307,6 +352,18 @@ module ol_framework::page_rank_lazy { record.cached_score } + #[view] + /// TODO: remove this in production, since it's an expensive operation + public fun calculate_score(addr: address): 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 score = traverse_graph(&roots, addr); + score + } + #[view] // check if it's stale public fun is_stale(addr: address): bool acquires UserTrustRecord { From e39f6d133441faf0a15d300458161aefc4379ef4 Mon Sep 17 00:00:00 2001 From: Gabi Beaver Date: Thu, 22 May 2025 17:25:22 -0400 Subject: [PATCH 04/25] improve efficiency, create depth limit, increase processed amount --- .../ol_sources/vouch_lib/page_rank_lazy.move | 73 +++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) 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 0ad050996..26b886650 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 @@ -29,7 +29,11 @@ module ol_framework::page_rank_lazy { /// Circuit breaker to prevent stack overflow during recursive graph traversal. /// Limits the total number of nodes processed in a single traversal. - const MAX_PROCESSED_ADDRESSES: u64 = 1_000; + const MAX_PROCESSED_ADDRESSES: u64 = 10_000; + + /// Maximum depth for path traversal in the trust graph. + /// This limits how far the algorithm will search from a root node. + const MAX_PATH_DEPTH: u64 = 10; /// Per-user trust record - each user stores their own trust data /// This resource tracks a user's cached trust score and staleness state. @@ -139,7 +143,7 @@ module ol_framework::page_rank_lazy { // 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, target, &mut visited, 2 * MAX_VOUCH_SCORE, 0 ); }; @@ -163,6 +167,7 @@ module ol_framework::page_rank_lazy { /// with distance from the source. /// 5. Special Root Handling: Prevents accumulation from interconnected root accounts to avoid /// artificial score inflation. + /// 6. Depth Limiting: Restricts traversal to a maximum path depth to prevent excessive recursion. /// /// The algorithm handles complex trust graphs including branching paths, merging paths /// (diamond patterns), and multiple routes from roots to targets. @@ -170,8 +175,14 @@ module ol_framework::page_rank_lazy { current: address, target: address, visited: &mut vector
, - current_power: u64 + current_power: u64, + current_depth: u64 ): u64 { + // Early termination: max depth reached + if (current_depth >= MAX_PATH_DEPTH) { + return 0 + }; + if(!vouch::is_init(current)) { return 0 }; @@ -191,8 +202,10 @@ module ol_framework::page_rank_lazy { let (neighbors, _) = vouch::get_given_vouches(current); let neighbor_count = vector::length(&neighbors); - diem_std::debug::print(¤t); - diem_std::debug::print(&neighbor_count); + + // Debug prints can be expensive in production, so we'll comment them out + // diem_std::debug::print(¤t); + // diem_std::debug::print(&neighbor_count); // No neighbors means no path if (neighbor_count == 0) { @@ -202,13 +215,18 @@ module ol_framework::page_rank_lazy { // Track total accumulation of scores from all unique paths let total_score = 0; + // Direct connection optimization: if target is a direct neighbor, count that path + if (vector::contains(&neighbors, &target)) { + // Calculate power passed to direct neighbor (50% decay) + let direct_power = current_power / 2; + total_score = total_score + direct_power; + }; + // Calculate power passed to neighbors (50% decay) // This division represents the power reduction for being one hop away from the current node let next_power = current_power / 2; - // 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 + // Special case for root of trust nodes if ( root_of_trust::is_root_at_registry(@diem_framework, current) && root_of_trust::is_root_at_registry(@diem_framework, target) && @@ -222,13 +240,17 @@ module ol_framework::page_rank_lazy { // Add current to visited BEFORE checking neighbors vector::push_back(visited, current); + // Increment depth for next level + let next_depth = current_depth + 1; + // Check ALL neighbors for paths to target let i = 0; while (i < neighbor_count) { let neighbor = *vector::borrow(&neighbors, i); - // Only explore if not already in our path (avoid cycles) - if (!vector::contains(visited, &neighbor)) { + // Only explore if not already in our path (avoid cycles) and not the direct target + // (since we already counted the direct path above) + if (!vector::contains(visited, &neighbor) && neighbor != target) { // Skip this path if the neighbor is a root of trust (other than target) // as we don't want to accumulate points from roots vouching for each other if ( @@ -243,12 +265,13 @@ module ol_framework::page_rank_lazy { // This ensures each path can be explored independently let path_visited = *visited; - // Continue search from this neighbor with reduced power + // Continue search from this neighbor with reduced power and increased depth let path_score = walk_from_node( neighbor, target, &mut path_visited, - next_power + next_power, + next_depth ); // Add to total score (accumulate from all paths) @@ -274,8 +297,13 @@ module ol_framework::page_rank_lazy { 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 + /// Internal helper function with cycle detection for marking nodes as stale + /// Uses vouch module to get outgoing vouches and implements optimizations to reduce + /// the number of nodes processed: + /// + /// 1. Cycle detection to avoid revisiting nodes + /// 2. Process limit to prevent excessive recursion + /// 3. Efficient traversal that prioritizes direct dependencies fun walk_stale( user: address, visited: &mut vector
, @@ -300,6 +328,12 @@ module ol_framework::page_rank_lazy { // 1. Mark its UserTrustRecord as stale if it exists. if (exists(user)) { let record = borrow_global_mut(user); + // Skip if already marked as stale to reduce redundant processing + if (record.is_stale) { + // Add this node to the visited set anyway to prevent re-traversal + vector::push_back(visited, user); + return + }; record.is_stale = true; }; @@ -322,6 +356,11 @@ 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) { + return + }; + 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) @@ -353,7 +392,11 @@ module ol_framework::page_rank_lazy { } #[view] - /// TODO: remove this in production, since it's an expensive operation + /// Calculates a fresh trust score without updating the cache. + /// This is an expensive operation that traverses the entire relevant trust graph. + /// + /// WARNING: This function is provided for diagnostic and testing purposes. + /// In production, use get_trust_score() or get_cached_score() instead. public fun calculate_score(addr: address): u64 { assert!(exists(addr), error::invalid_state(ENOT_INITIALIZED)); // Cache is stale or expired - compute fresh score From 20793cf5ced5c56d329f09b74b7484ff494e2538 Mon Sep 17 00:00:00 2001 From: Franci Tempo Date: Fri, 23 May 2025 12:08:17 -0400 Subject: [PATCH 05/25] add more simple tests --- .../ol_sources/tests/page_rank.test.move | 178 +++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) 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 4aeb67b47..729914465 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 @@ -526,15 +526,188 @@ module ol_framework::test_page_rank { }; } + #[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)] /// When a vouch branch occurs, /// and then merges back to a user, /// the transitive scores should accumulate // 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); + 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)] + /// 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 root0 + // 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)] + /// Diamond pattern when alice has two vouches from root + // Root0 -> Alice + // Root0 -> Bob -> Alice // Root1 -> Alice - // Root0 -> Bob -> Alice - fun diamond_pattern_accumulates(framework: &signer) { + fun diamond_patter_two_roots(framework: &signer) { // Set up the test base let roots_sig = test_base(framework); @@ -581,7 +754,6 @@ module ol_framework::test_page_rank { 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); - } From bd472796bce42ccbebf1dc49f1e2d58b5fc5aefd Mon Sep 17 00:00:00 2001 From: Rosina Fitz Lento Date: Fri, 23 May 2025 12:12:59 -0400 Subject: [PATCH 06/25] cycle detection tests --- .../ol_sources/tests/page_rank.test.move | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) 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 729914465..6c92cbd50 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 @@ -656,7 +656,7 @@ module ol_framework::test_page_rank { assert!(alice_score_post == 100_000, 7357002); } - #[test(framework = @ol_framework)] + #[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) { @@ -695,13 +695,70 @@ module ol_framework::test_page_rank { 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 root0 + // 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)] /// Diamond pattern when alice has two vouches from root // Root0 -> Alice From b6d10d23f127ac74d5f5e38e06ec783ea02854bb Mon Sep 17 00:00:00 2001 From: Beauregard O'Sforzando Date: Fri, 23 May 2025 12:26:38 -0400 Subject: [PATCH 07/25] implements backtracking efficiently, restores the max processed address check --- .../ol_sources/vouch_lib/page_rank_lazy.move | 63 ++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) 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 26b886650..c6ebd78c5 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 @@ -29,7 +29,7 @@ module ol_framework::page_rank_lazy { /// Circuit breaker to prevent stack overflow during recursive graph traversal. /// Limits the total number of nodes processed in a single traversal. - const MAX_PROCESSED_ADDRESSES: u64 = 10_000; + const MAX_PROCESSED_ADDRESSES: u64 = 1_000; /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. @@ -129,28 +129,23 @@ module ol_framework::page_rank_lazy { let total_score = 0; let root_idx = 0; let roots_len = vector::length(roots); + let processed_count: u64 = 0; // Track total processed nodes globally // For each root, calculate its contribution independently while (root_idx < roots_len) { + // Check if the global limit for processed nodes has been reached + if (processed_count >= MAX_PROCESSED_ADDRESSES) { + break + }; let root = *vector::borrow(roots, root_idx); - let visited = vector::empty
(); - // Create an initial empty visited set for each root traversal - // No need to add root to visited at this level since walk_from_node handles it - 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, 0 + root, target, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count ); }; - - root_idx = root_idx + 1; }; - total_score } @@ -176,13 +171,26 @@ module ol_framework::page_rank_lazy { target: address, visited: &mut vector
, current_power: u64, - current_depth: u64 + current_depth: u64, + processed_count: &mut u64 ): u64 { + // Always check the global processed count limit first + if (*processed_count >= MAX_PROCESSED_ADDRESSES) { + return 0 + }; + *processed_count = *processed_count + 1; + // Early termination: max depth reached if (current_depth >= MAX_PATH_DEPTH) { return 0 }; + // Check if the global limit for processed nodes has been reached + if (*processed_count >= MAX_PROCESSED_ADDRESSES) { + return 0 + }; + *processed_count = *processed_count + 1; + if(!vouch::is_init(current)) { return 0 }; @@ -239,20 +247,11 @@ module ol_framework::page_rank_lazy { // Add current to visited BEFORE checking neighbors vector::push_back(visited, current); - - // Increment depth for next level let next_depth = current_depth + 1; - - // Check ALL neighbors for paths to target let i = 0; while (i < neighbor_count) { let neighbor = *vector::borrow(&neighbors, i); - - // Only explore if not already in our path (avoid cycles) and not the direct target - // (since we already counted the direct path above) if (!vector::contains(visited, &neighbor) && neighbor != target) { - // Skip this path if the neighbor is a root of trust (other than target) - // as we don't want to accumulate points from roots vouching for each other if ( neighbor != target && root_of_trust::is_root_at_registry(@diem_framework, neighbor) @@ -260,29 +259,21 @@ module ol_framework::page_rank_lazy { i = i + 1; continue }; - - // Create a copy of visited for this path exploration - // This ensures each path can be explored independently - let path_visited = *visited; - - // Continue search from this neighbor with reduced power and increased depth + // Pass the same mutable visited vector, and backtrack after recursion let path_score = walk_from_node( neighbor, target, - &mut path_visited, + visited, next_power, - next_depth + next_depth, + processed_count ); - - // Add to total score (accumulate from all paths) - // This ensures that when multiple paths lead to the target ("diamond pattern"), - // all valid paths contribute to the final score rather than only taking the best one total_score = total_score + path_score; }; - i = i + 1; }; - + // Backtrack: remove current from visited after all neighbors processed + vector::pop_back(visited); total_score } From f572aebbcc1cf56c35ee70a360481971ec40b89f Mon Sep 17 00:00:00 2001 From: Basil Saint Urchin Date: Fri, 23 May 2025 12:52:41 -0400 Subject: [PATCH 08/25] add asymmetrical diamond test --- .../ol_sources/tests/page_rank.test.move | 203 ++++++++++++++---- 1 file changed, 156 insertions(+), 47 deletions(-) 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 6c92cbd50..a25831ac0 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 @@ -565,52 +565,6 @@ module ol_framework::test_page_rank { assert!(carol_score_post == 25_000, 7357002); } - #[test(framework = @ol_framework)] - /// When a vouch branch occurs, - /// and then merges back to a user, - /// the transitive scores should accumulate - // 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); - 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)] /// A full cycle of vouches, outside of root of trust // Root0 -> Alice -> Bob -> Carol -> Alice @@ -759,12 +713,167 @@ module ol_framework::test_page_rank { 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 + // 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); + 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 + vouch_txs::vouch_for(&alice_sig, dave_addr); + + // bob vouches for carol who vouches for dave + 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 + 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 dave + vouch_txs::vouch_for(&alice_sig, carol_addr); + vouch_txs::vouch_for(&carol_sig, dave_addr); + + // bob vouches for carol who vouches for dave + vouch_txs::vouch_for(&bob_sig, carol_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_patter_two_roots(framework: &signer) { + fun diamond_pattern_two_roots(framework: &signer) { // Set up the test base let roots_sig = test_base(framework); From d29cb4db620ae02728adfa40472cd087a599eb6f Mon Sep 17 00:00:00 2001 From: Vale De Bittern Date: Fri, 23 May 2025 13:08:44 -0400 Subject: [PATCH 09/25] wip debugging asymetrical diamond --- .../ol_sources/tests/page_rank.test.move | 1 + .../ol_sources/vouch_lib/page_rank_lazy.move | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) 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 a25831ac0..81a01930d 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 @@ -809,6 +809,7 @@ module ol_framework::test_page_rank { 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); } 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 c6ebd78c5..0fb25b3fc 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 @@ -3,6 +3,7 @@ module ol_framework::page_rank_lazy { use std::signer; use std::timestamp; use std::vector; + use std::string; use ol_framework::vouch; use ol_framework::root_of_trust; @@ -211,9 +212,11 @@ module ol_framework::page_rank_lazy { let (neighbors, _) = vouch::get_given_vouches(current); let neighbor_count = vector::length(&neighbors); - // Debug prints can be expensive in production, so we'll comment them out - // diem_std::debug::print(¤t); - // diem_std::debug::print(&neighbor_count); + // Debug path traversal + if (current_depth == 0) { + diem_std::debug::print(&string::utf8(b"Starting from root:")); + diem_std::debug::print(¤t); + }; // No neighbors means no path if (neighbor_count == 0) { @@ -227,6 +230,10 @@ module ol_framework::page_rank_lazy { if (vector::contains(&neighbors, &target)) { // Calculate power passed to direct neighbor (50% decay) let direct_power = current_power / 2; + diem_std::debug::print(&string::utf8(b"Direct path found from:")); + diem_std::debug::print(¤t); + diem_std::debug::print(&string::utf8(b"to target with power:")); + diem_std::debug::print(&direct_power); total_score = total_score + direct_power; }; @@ -253,27 +260,31 @@ module ol_framework::page_rank_lazy { let neighbor = *vector::borrow(&neighbors, i); if (!vector::contains(visited, &neighbor) && neighbor != target) { if ( - neighbor != target && root_of_trust::is_root_at_registry(@diem_framework, neighbor) ) { i = i + 1; continue }; - // Pass the same mutable visited vector, and backtrack after recursion + // Create a copy of visited for this branch to allow independent path exploration + let visited_copy = *visited; let path_score = walk_from_node( neighbor, target, - visited, + &mut visited_copy, next_power, next_depth, processed_count ); + if (path_score > 0) { + diem_std::debug::print(&string::utf8(b"Found path via neighbor:")); + diem_std::debug::print(&neighbor); + diem_std::debug::print(&string::utf8(b"with score:")); + diem_std::debug::print(&path_score); + }; total_score = total_score + path_score; }; i = i + 1; }; - // Backtrack: remove current from visited after all neighbors processed - vector::pop_back(visited); total_score } From a7446bb0dc334d2fe09fee9dba50ccaf2ecd5f0d Mon Sep 17 00:00:00 2001 From: Beauregard Legato Date: Fri, 23 May 2025 13:15:46 -0400 Subject: [PATCH 10/25] reverts to proper backtracking logic. --- .../ol_sources/tests/page_rank.test.move | 10 +++++----- .../ol_sources/vouch_lib/page_rank_lazy.move | 17 +---------------- 2 files changed, 6 insertions(+), 21 deletions(-) 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 81a01930d..ade53c33d 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 @@ -800,10 +800,10 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(root0, alice_addr); vouch_txs::vouch_for(root0, bob_addr); - // alice vouches for dave + // alice vouches for dave (short path) vouch_txs::vouch_for(&alice_sig, dave_addr); - // bob vouches for carol who vouches for dave + // 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); @@ -856,12 +856,12 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(root0, alice_addr); vouch_txs::vouch_for(root0, bob_addr); - // alice vouches for dave + // 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 carol who vouches for dave - vouch_txs::vouch_for(&bob_sig, carol_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 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 0fb25b3fc..2f7731aa2 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 @@ -3,7 +3,6 @@ module ol_framework::page_rank_lazy { use std::signer; use std::timestamp; use std::vector; - use std::string; use ol_framework::vouch; use ol_framework::root_of_trust; @@ -212,11 +211,7 @@ module ol_framework::page_rank_lazy { let (neighbors, _) = vouch::get_given_vouches(current); let neighbor_count = vector::length(&neighbors); - // Debug path traversal - if (current_depth == 0) { - diem_std::debug::print(&string::utf8(b"Starting from root:")); - diem_std::debug::print(¤t); - }; + // Debug path traversal - removed for performance // No neighbors means no path if (neighbor_count == 0) { @@ -230,10 +225,6 @@ module ol_framework::page_rank_lazy { if (vector::contains(&neighbors, &target)) { // Calculate power passed to direct neighbor (50% decay) let direct_power = current_power / 2; - diem_std::debug::print(&string::utf8(b"Direct path found from:")); - diem_std::debug::print(¤t); - diem_std::debug::print(&string::utf8(b"to target with power:")); - diem_std::debug::print(&direct_power); total_score = total_score + direct_power; }; @@ -275,12 +266,6 @@ module ol_framework::page_rank_lazy { next_depth, processed_count ); - if (path_score > 0) { - diem_std::debug::print(&string::utf8(b"Found path via neighbor:")); - diem_std::debug::print(&neighbor); - diem_std::debug::print(&string::utf8(b"with score:")); - diem_std::debug::print(&path_score); - }; total_score = total_score + path_score; }; i = i + 1; From c8c668dee64ab94d05557873b76f444a658109f6 Mon Sep 17 00:00:00 2001 From: Chiaretta Saint Adagio Date: Fri, 23 May 2025 13:41:47 -0400 Subject: [PATCH 11/25] abort on max processed --- .../vouch_lib/distance_to_root.move | 5 ++--- .../ol_sources/vouch_lib/page_rank_lazy.move | 20 +++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) 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 2f7731aa2..81eba129e 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 @@ -134,9 +134,7 @@ module ol_framework::page_rank_lazy { // For each root, calculate its contribution independently while (root_idx < roots_len) { // Check if the global limit for processed nodes has been reached - if (processed_count >= MAX_PROCESSED_ADDRESSES) { - break - }; + assert!(processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); let root = *vector::borrow(roots, root_idx); let visited = vector::empty
(); if (root != target) { @@ -175,9 +173,7 @@ module ol_framework::page_rank_lazy { processed_count: &mut u64 ): u64 { // Always check the global processed count limit first - if (*processed_count >= MAX_PROCESSED_ADDRESSES) { - return 0 - }; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); *processed_count = *processed_count + 1; // Early termination: max depth reached @@ -186,9 +182,7 @@ module ol_framework::page_rank_lazy { }; // Check if the global limit for processed nodes has been reached - if (*processed_count >= MAX_PROCESSED_ADDRESSES) { - return 0 - }; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); *processed_count = *processed_count + 1; if(!vouch::is_init(current)) { @@ -304,9 +298,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; @@ -344,9 +336,7 @@ module ol_framework::page_rank_lazy { let len = vector::length(&outgoing_vouches); while (i < len) { // Check again if we've hit the processing limit - if (*processed_count >= MAX_PROCESSED_ADDRESSES) { - return - }; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); let each_vouchee = vector::borrow(&outgoing_vouches, i); // Pass the same mutable reference to processed_count. From 6dc540c7126202013df4ead1f9262c31d0fbcd3c Mon Sep 17 00:00:00 2001 From: Sandra Stag Date: Fri, 23 May 2025 13:51:12 -0400 Subject: [PATCH 12/25] patch max depth --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 81eba129e..a392f1e83 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 @@ -172,16 +172,12 @@ module ol_framework::page_rank_lazy { current_depth: u64, processed_count: &mut u64 ): u64 { - // Always check the global processed count limit first - assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); - *processed_count = *processed_count + 1; - // Early termination: max depth reached if (current_depth >= MAX_PATH_DEPTH) { return 0 }; - // Check if the global limit for processed nodes has been reached + // Always check the global processed count limit before processing this node assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); *processed_count = *processed_count + 1; @@ -216,7 +212,8 @@ module ol_framework::page_rank_lazy { let total_score = 0; // Direct connection optimization: if target is a direct neighbor, count that path - if (vector::contains(&neighbors, &target)) { + // But only if we haven't reached the depth limit for the next hop + if (vector::contains(&neighbors, &target) && current_depth + 1 < MAX_PATH_DEPTH) { // Calculate power passed to direct neighbor (50% decay) let direct_power = current_power / 2; total_score = total_score + direct_power; From cb402f23ff7b572e3d57223f85b8558583f7c132 Mon Sep 17 00:00:00 2001 From: Vale McPiano Date: Fri, 23 May 2025 13:57:10 -0400 Subject: [PATCH 13/25] test lower path depth --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a392f1e83..96ffe933d 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 @@ -33,7 +33,7 @@ module ol_framework::page_rank_lazy { /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. - const MAX_PATH_DEPTH: u64 = 10; + const MAX_PATH_DEPTH: u64 = 4; /// Per-user trust record - each user stores their own trust data /// This resource tracks a user's cached trust score and staleness state. From 51a3bb17ea3ed0ee674e9264890732f9676b1f77 Mon Sep 17 00:00:00 2001 From: Sofi Forte Date: Fri, 23 May 2025 14:00:25 -0400 Subject: [PATCH 14/25] test 8 levels depth --- .../ol_sources/vouch_lib/page_rank_lazy.move | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) 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 96ffe933d..1c9d818d8 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 @@ -33,7 +33,7 @@ module ol_framework::page_rank_lazy { /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. - const MAX_PATH_DEPTH: u64 = 4; + const MAX_PATH_DEPTH: u64 = 8; /// Per-user trust record - each user stores their own trust data /// This resource tracks a user's cached trust score and staleness state. @@ -134,7 +134,11 @@ module ol_framework::page_rank_lazy { // For each root, calculate its contribution independently while (root_idx < roots_len) { // Check if the global limit for processed nodes has been reached - assert!(processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); + if (processed_count >= MAX_PROCESSED_ADDRESSES - 50) { + // Stop processing additional roots if we're close to the limit + break + }; + let root = *vector::borrow(roots, root_idx); let visited = vector::empty
(); if (root != target) { @@ -172,31 +176,34 @@ module ol_framework::page_rank_lazy { current_depth: u64, processed_count: &mut u64 ): u64 { - // Early termination: max depth reached + // Early termination: max depth reached - don't consume processing budget if (current_depth >= MAX_PATH_DEPTH) { return 0 }; - // Always check the global processed count limit before processing this node - assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); - *processed_count = *processed_count + 1; + // Early termination: already visited (cycle detection) - don't consume processing budget + if (vector::contains(visited, ¤t)) { + return 0 + }; + // Early termination: not initialized - don't consume processing budget if(!vouch::is_init(current)) { return 0 }; - // 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 + // Early termination: power too low - don't consume processing budget + if (current_power < 2) { + return 0 + }; + + // Early termination: found target - don't consume processing budget for this simple case if (current == target) { return current_power }; - // Stop condition - only stop if power is too low - if (current_power < 2) { - return 0 - }; + // Only now do we consume processing budget since we're doing real work + 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); @@ -239,6 +246,12 @@ module ol_framework::page_rank_lazy { let next_depth = current_depth + 1; let i = 0; while (i < neighbor_count) { + // Check if we're approaching the processing limit before exploring more neighbors + if (*processed_count >= MAX_PROCESSED_ADDRESSES - 10) { + // Stop exploring neighbors if we're too close to the limit + break + }; + let neighbor = *vector::borrow(&neighbors, i); if (!vector::contains(visited, &neighbor) && neighbor != target) { if ( From f66e42efc208bb6b67719f12b4d3994a7bc33294 Mon Sep 17 00:00:00 2001 From: Archibald De Bittern Date: Fri, 23 May 2025 14:04:19 -0400 Subject: [PATCH 15/25] tune performance --- .../ol_sources/vouch_lib/page_rank_lazy.move | 108 ++++++------------ 1 file changed, 35 insertions(+), 73 deletions(-) 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 1c9d818d8..10ef3b643 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 @@ -176,101 +176,69 @@ module ol_framework::page_rank_lazy { current_depth: u64, processed_count: &mut u64 ): u64 { - // Early termination: max depth reached - don't consume processing budget - if (current_depth >= MAX_PATH_DEPTH) { - return 0 - }; - - // Early termination: already visited (cycle detection) - don't consume processing budget - if (vector::contains(visited, ¤t)) { - return 0 - }; - - // Early termination: not initialized - don't consume processing budget - if(!vouch::is_init(current)) { - return 0 - }; - - // Early termination: power too low - don't consume processing budget - if (current_power < 2) { - return 0 - }; - - // Early termination: found target - don't consume processing budget for this simple case - if (current == target) { - return current_power - }; - - // Only now do we consume processing budget since we're doing real work - assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); + // Early terminations that don't consume processing budget + if (current_depth >= MAX_PATH_DEPTH) return 0; + if (vector::contains(visited, ¤t)) return 0; + if (!vouch::is_init(current)) return 0; + if (current_power < 2) return 0; + if (current == target) return current_power; + + // Budget check and consumption + if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; *processed_count = *processed_count + 1; let (neighbors, _) = vouch::get_given_vouches(current); let neighbor_count = vector::length(&neighbors); - // Debug path traversal - removed for performance - - // No neighbors means no path - if (neighbor_count == 0) { - return 0 - }; + if (neighbor_count == 0) return 0; - // Track total accumulation of scores from all unique paths let total_score = 0; - // Direct connection optimization: if target is a direct neighbor, count that path - // But only if we haven't reached the depth limit for the next hop + // Direct connection check if (vector::contains(&neighbors, &target) && current_depth + 1 < MAX_PATH_DEPTH) { - // Calculate power passed to direct neighbor (50% decay) - let direct_power = current_power / 2; - total_score = total_score + direct_power; + total_score = total_score + (current_power / 2); }; - // Calculate power passed to neighbors (50% decay) - // This division represents the power reduction for being one hop away from the current node let next_power = current_power / 2; - // Special case for root of trust nodes + // Special case for root-to-root vouching 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::contains(&neighbors, &target) ) { vector::push_back(visited, current); return next_power }; - // Add current to visited BEFORE checking neighbors + // Add current to visited and explore neighbors vector::push_back(visited, current); let next_depth = current_depth + 1; + + // CRITICAL: Limit number of neighbors explored to prevent exponential explosion + let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; + let neighbors_explored = 0; let i = 0; - while (i < neighbor_count) { - // Check if we're approaching the processing limit before exploring more neighbors - if (*processed_count >= MAX_PROCESSED_ADDRESSES - 10) { - // Stop exploring neighbors if we're too close to the limit - break - }; + + while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { + if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; let neighbor = *vector::borrow(&neighbors, i); if (!vector::contains(visited, &neighbor) && neighbor != target) { - if ( - root_of_trust::is_root_at_registry(@diem_framework, neighbor) - ) { - i = i + 1; - continue + if (!root_of_trust::is_root_at_registry(@diem_framework, neighbor)) { + let visited_copy = *visited; + let path_score = walk_from_node( + neighbor, + target, + &mut visited_copy, + next_power, + next_depth, + processed_count + ); + total_score = total_score + path_score; + neighbors_explored = neighbors_explored + 1; }; - // Create a copy of visited for this branch to allow independent path exploration - let visited_copy = *visited; - let path_score = walk_from_node( - neighbor, - target, - &mut visited_copy, - next_power, - next_depth, - processed_count - ); - total_score = total_score + path_score; }; i = i + 1; }; @@ -317,12 +285,6 @@ module ol_framework::page_rank_lazy { // 1. Mark its UserTrustRecord as stale if it exists. if (exists(user)) { let record = borrow_global_mut(user); - // Skip if already marked as stale to reduce redundant processing - if (record.is_stale) { - // Add this node to the visited set anyway to prevent re-traversal - vector::push_back(visited, user); - return - }; record.is_stale = true; }; @@ -346,7 +308,7 @@ module ol_framework::page_rank_lazy { let len = vector::length(&outgoing_vouches); while (i < len) { // Check again if we've hit the processing limit - assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); + if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; let each_vouchee = vector::borrow(&outgoing_vouches, i); // Pass the same mutable reference to processed_count. From 8a15dd4556eb97b33206b407d1d418c4208b05f5 Mon Sep 17 00:00:00 2001 From: Paoletta De Lento Date: Fri, 23 May 2025 14:07:36 -0400 Subject: [PATCH 16/25] lower max depth --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 10ef3b643..4e2a235ea 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 @@ -33,7 +33,7 @@ module ol_framework::page_rank_lazy { /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. - const MAX_PATH_DEPTH: u64 = 8; + const MAX_PATH_DEPTH: u64 = 4; /// Per-user trust record - each user stores their own trust data /// This resource tracks a user's cached trust score and staleness state. From df0ce378308a37f01e92e13068e50d6a238d1688 Mon Sep 17 00:00:00 2001 From: Mariella Legato Date: Fri, 23 May 2025 14:09:44 -0400 Subject: [PATCH 17/25] bump max process to 10K --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4e2a235ea..8fded1c52 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 @@ -29,7 +29,7 @@ module ol_framework::page_rank_lazy { /// Circuit breaker to prevent stack overflow during recursive graph traversal. /// Limits the total number of nodes processed in a single traversal. - const MAX_PROCESSED_ADDRESSES: u64 = 1_000; + const MAX_PROCESSED_ADDRESSES: u64 = 10_000; /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. From ccfa1fff971ba1e678269302661e9ee58a268c83 Mon Sep 17 00:00:00 2001 From: Rosina Von Stag Date: Fri, 23 May 2025 14:11:01 -0400 Subject: [PATCH 18/25] bump max depth --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8fded1c52..7f24772a9 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 @@ -33,7 +33,7 @@ module ol_framework::page_rank_lazy { /// Maximum depth for path traversal in the trust graph. /// This limits how far the algorithm will search from a root node. - const MAX_PATH_DEPTH: u64 = 4; + const MAX_PATH_DEPTH: u64 = 5; /// Per-user trust record - each user stores their own trust data /// This resource tracks a user's cached trust score and staleness state. From 4b49f932f00e3a5431216ccce11013d41e1f2f8b Mon Sep 17 00:00:00 2001 From: Rosina Fortissimo Date: Fri, 23 May 2025 14:22:07 -0400 Subject: [PATCH 19/25] test reverse walk --- .../ol_sources/vouch_lib/page_rank_lazy.move | 113 ++++++++++++++---- 1 file changed, 87 insertions(+), 26 deletions(-) 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 7f24772a9..3497449c1 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 @@ -112,42 +112,103 @@ module ol_framework::page_rank_lazy { score } - /// Simplified graph traversal that finds all valid paths from each root of trust to the target address. - /// This function iterates through each root in the provided list and accumulates scores from all - /// paths that lead to the target. + /// REVERSE WALK EXPERIMENT: Traverse graph by walking backwards from target to roots of trust. + /// Instead of walking from all roots outward to a target user (using given_vouches), this + /// experimental approach walks from a target user backwards toward the roots of trust by + /// accessing received_vouches. This should reduce processing budget by avoiding dead-end paths. /// - /// For each root, it: - /// 1. Creates a new empty visited set to track paths independently - /// 2. Calculates the score contribution via walk_from_node, which explores all possible paths - /// 3. Adds the score to the total accumulation - /// - /// The total accumulated score represents the combined trust value from all roots to the target. + /// The algorithm: + /// 1. Starts from the target user + /// 2. Walks backwards through the vouch graph using received_vouches + /// 3. Accumulates trust score when reaching any root of trust + /// 4. Avoids exploring dead-end paths that don't lead to roots fun traverse_graph( roots: &vector
, target: address, ): u64 { + let processed_count: u64 = 0; + let visited = vector::empty
(); + + // Start the reverse walk from the target + walk_backwards_from_target( + target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count + ) + } + + /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. + /// This experimental function implements the reverse walk algorithm that starts from the target user + /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. + /// + /// Key differences from forward walk: + /// 1. Starts from target instead of iterating through all roots + /// 2. Uses get_received_vouches() instead of get_given_vouches() to traverse backwards + /// 3. Accumulates score when reaching any root in the roots vector + /// 4. Avoids exploring dead-end paths that don't lead to roots + /// 5. Maintains the same trust decay and cycle detection principles + /// + /// This should reduce processing budget by avoiding dead-end exploration. + fun walk_backwards_from_target( + current: address, + roots: &vector
, + visited: &mut vector
, + current_power: u64, + current_depth: u64, + processed_count: &mut u64 + ): u64 { + // Early terminations that don't consume processing budget + if (current_depth >= MAX_PATH_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 + }; + + // Budget check and consumption + if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; + *processed_count = *processed_count + 1; + + // Get who vouched FOR this current user (backwards direction) + let (received_from, _) = vouch::get_received_vouches(current); + let neighbor_count = vector::length(&received_from); + + if (neighbor_count == 0) return 0; + let total_score = 0; - let root_idx = 0; - let roots_len = vector::length(roots); - let processed_count: u64 = 0; // Track total processed nodes globally - - // For each root, calculate its contribution independently - while (root_idx < roots_len) { - // Check if the global limit for processed nodes has been reached - if (processed_count >= MAX_PROCESSED_ADDRESSES - 50) { - // Stop processing additional roots if we're close to the limit - break - }; + let next_power = current_power / 2; + let next_depth = current_depth + 1; + + // Add current to visited and explore received vouches (backwards) + vector::push_back(visited, current); + + // CRITICAL: Limit number of neighbors explored to prevent exponential explosion + let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; + let neighbors_explored = 0; + let i = 0; - let root = *vector::borrow(roots, root_idx); - let visited = vector::empty
(); - if (root != target) { - total_score = total_score + walk_from_node( - root, target, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count + while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { + if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; + + let neighbor = *vector::borrow(&received_from, i); + if (!vector::contains(visited, &neighbor)) { + // Create a copy of visited for this path + let visited_copy = *visited; + let path_score = walk_backwards_from_target( + neighbor, + roots, + &mut visited_copy, + next_power, + next_depth, + processed_count ); + total_score = total_score + path_score; + neighbors_explored = neighbors_explored + 1; }; - root_idx = root_idx + 1; + i = i + 1; }; + total_score } From d15663692adb25e8142a542ea0aa1dba929baeb2 Mon Sep 17 00:00:00 2001 From: Isa Saint Bittern Date: Fri, 23 May 2025 14:30:32 -0400 Subject: [PATCH 20/25] calculate score shows depth and units processed --- .../ol_sources/tests/page_rank.test.move | 20 ++-- .../ol_sources/vouch_lib/page_rank_lazy.move | 110 +++++++++++++++++- 2 files changed, 117 insertions(+), 13 deletions(-) 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 ade53c33d..5ba15af20 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 @@ -561,7 +561,7 @@ module ol_framework::test_page_rank { 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); + let (carol_score_post, _, _) = page_rank_lazy::calculate_score(carol_addr); assert!(carol_score_post == 25_000, 7357002); } @@ -601,12 +601,12 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(&carol_sig, alice_addr); // no change from single path - let carol_score_post = page_rank_lazy::calculate_score(carol_addr); + 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); + let (alice_score_post, _, _) = page_rank_lazy::calculate_score(alice_addr); assert!(alice_score_post == 100_000, 7357002); } @@ -646,12 +646,12 @@ module ol_framework::test_page_rank { 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); + 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); + 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); } @@ -704,12 +704,12 @@ module ol_framework::test_page_rank { vouch_txs::vouch_for(&dave_sig, alice_addr); // no change from single path - let carol_score_post = page_rank_lazy::calculate_score(carol_addr); + 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); + let (alice_score_post, _, _) = page_rank_lazy::calculate_score(alice_addr); assert!(alice_score_post == 100_000, 7357002); } @@ -754,7 +754,7 @@ module ol_framework::test_page_rank { 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); + 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); } @@ -807,7 +807,7 @@ module ol_framework::test_page_rank { 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); + 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); @@ -863,7 +863,7 @@ module ol_framework::test_page_rank { // 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); + 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); 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 3497449c1..ba25feb4d 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 @@ -135,6 +135,24 @@ module ol_framework::page_rank_lazy { ) } + /// REVERSE WALK EXPERIMENT: Traverse graph by walking backwards from target to roots of trust. + /// This version returns additional statistics: (score, max_depth_reached, accounts_processed) + fun traverse_graph_with_stats( + roots: &vector
, + target: address, + ): (u64, u64, u64) { + let processed_count: u64 = 0; + let max_depth_reached: u64 = 0; + let visited = vector::empty
(); + + // Start the reverse walk from the target + let score = walk_backwards_from_target_with_stats( + target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached + ); + + (score, max_depth_reached, processed_count) + } + /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. /// This experimental function implements the reverse walk algorithm that starts from the target user /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. @@ -212,6 +230,90 @@ module ol_framework::page_rank_lazy { total_score } + /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. + /// This experimental function implements the reverse walk algorithm that starts from the target user + /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. + /// + /// Key differences from forward walk: + /// 1. Starts from target instead of iterating through all roots + /// 2. Uses get_received_vouches() instead of get_given_vouches() to traverse backwards + /// 3. Accumulates score when reaching any root in the roots vector + /// 4. Avoids exploring dead-end paths that don't lead to roots + /// 5. Maintains the same trust decay and cycle detection principles + /// + /// This should reduce processing budget by avoiding dead-end exploration. + fun walk_backwards_from_target_with_stats( + current: address, + roots: &vector
, + visited: &mut vector
, + current_power: u64, + current_depth: u64, + processed_count: &mut u64, + max_depth_reached: &mut u64 + ): u64 { + // Track maximum depth reached + if (current_depth > *max_depth_reached) { + *max_depth_reached = current_depth; + }; + + // Early terminations that don't consume processing budget + if (current_depth >= MAX_PATH_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 + }; + + // Budget check and consumption + if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; + *processed_count = *processed_count + 1; + + // Get who vouched FOR this current user (backwards direction) + let (received_from, _) = vouch::get_received_vouches(current); + let neighbor_count = vector::length(&received_from); + + if (neighbor_count == 0) return 0; + + let total_score = 0; + let next_power = current_power / 2; + let next_depth = current_depth + 1; + + // Add current to visited and explore received vouches (backwards) + vector::push_back(visited, current); + + // CRITICAL: Limit number of neighbors explored to prevent exponential explosion + let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; + let neighbors_explored = 0; + let i = 0; + + while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { + if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; + + let neighbor = *vector::borrow(&received_from, i); + if (!vector::contains(visited, &neighbor)) { + // Create a copy of visited for this path + let visited_copy = *visited; + let path_score = walk_backwards_from_target_with_stats( + neighbor, + roots, + &mut visited_copy, + next_power, + next_depth, + processed_count, + max_depth_reached + ); + total_score = total_score + path_score; + neighbors_explored = neighbors_explored + 1; + }; + i = i + 1; + }; + + total_score + } + /// Advanced graph traversal algorithm that finds and accumulates scores from all valid paths /// from a starting node to a target. This function follows these principles: /// @@ -407,14 +509,16 @@ module ol_framework::page_rank_lazy { /// /// WARNING: This function is provided for diagnostic and testing purposes. /// In production, use get_trust_score() or get_cached_score() instead. - public fun calculate_score(addr: address): u64 { + /// + /// Returns: (score, max_depth_reached, accounts_processed) + 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 score = traverse_graph(&roots, addr); - score + let (score, max_depth, processed_count) = traverse_graph_with_stats(&roots, addr); + (score, max_depth, processed_count) } #[view] From edbd6b918501e6bc234fb6074189f930651b728c Mon Sep 17 00:00:00 2001 From: Isa McMezzo Date: Fri, 23 May 2025 14:37:19 -0400 Subject: [PATCH 21/25] cleanup --- .../ol_sources/tests/page_rank.test.move | 2 +- .../ol_sources/vouch_lib/page_rank_lazy.move | 178 +----------------- 2 files changed, 5 insertions(+), 175 deletions(-) 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 5ba15af20..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) { 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 ba25feb4d..708dc6d09 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 @@ -127,11 +127,12 @@ module ol_framework::page_rank_lazy { target: address, ): u64 { let processed_count: u64 = 0; + let max_depth_reached: u64 = 0; let visited = vector::empty
(); - // Start the reverse walk from the target - walk_backwards_from_target( - target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count + // Start the reverse walk from the target and extract just the score + walk_backwards_from_target_with_stats( + target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached ) } @@ -153,83 +154,6 @@ module ol_framework::page_rank_lazy { (score, max_depth_reached, processed_count) } - /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. - /// This experimental function implements the reverse walk algorithm that starts from the target user - /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. - /// - /// Key differences from forward walk: - /// 1. Starts from target instead of iterating through all roots - /// 2. Uses get_received_vouches() instead of get_given_vouches() to traverse backwards - /// 3. Accumulates score when reaching any root in the roots vector - /// 4. Avoids exploring dead-end paths that don't lead to roots - /// 5. Maintains the same trust decay and cycle detection principles - /// - /// This should reduce processing budget by avoiding dead-end exploration. - fun walk_backwards_from_target( - current: address, - roots: &vector
, - visited: &mut vector
, - current_power: u64, - current_depth: u64, - processed_count: &mut u64 - ): u64 { - // Early terminations that don't consume processing budget - if (current_depth >= MAX_PATH_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 - }; - - // Budget check and consumption - if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; - *processed_count = *processed_count + 1; - - // Get who vouched FOR this current user (backwards direction) - let (received_from, _) = vouch::get_received_vouches(current); - let neighbor_count = vector::length(&received_from); - - if (neighbor_count == 0) return 0; - - let total_score = 0; - let next_power = current_power / 2; - let next_depth = current_depth + 1; - - // Add current to visited and explore received vouches (backwards) - vector::push_back(visited, current); - - // CRITICAL: Limit number of neighbors explored to prevent exponential explosion - let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; - let neighbors_explored = 0; - let i = 0; - - while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { - if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; - - let neighbor = *vector::borrow(&received_from, i); - if (!vector::contains(visited, &neighbor)) { - // Create a copy of visited for this path - let visited_copy = *visited; - let path_score = walk_backwards_from_target( - neighbor, - roots, - &mut visited_copy, - next_power, - next_depth, - processed_count - ); - total_score = total_score + path_score; - neighbors_explored = neighbors_explored + 1; - }; - i = i + 1; - }; - - total_score - } - /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. /// This experimental function implements the reverse walk algorithm that starts from the target user /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. @@ -314,100 +238,6 @@ module ol_framework::page_rank_lazy { total_score } - /// Advanced graph traversal algorithm that finds and accumulates scores from all valid paths - /// from a starting node to a target. This function follows these principles: - /// - /// 1. Cycle Detection: Uses the visited set to avoid revisiting nodes already in the current path. - /// 2. Path Independence: Creates a copy of the visited set for each branch, ensuring separate paths - /// are explored independently. - /// 3. Score Accumulation: Accumulates scores from all valid and unique paths rather than only - /// returning the maximum score. This ensures "diamond patterns" (where multiple paths lead to - /// the same target) properly accumulate their trust contributions. - /// 4. Trust Decay: Implements a 50% power reduction per hop, representing diminishing trust - /// with distance from the source. - /// 5. Special Root Handling: Prevents accumulation from interconnected root accounts to avoid - /// artificial score inflation. - /// 6. Depth Limiting: Restricts traversal to a maximum path depth to prevent excessive recursion. - /// - /// The algorithm handles complex trust graphs including branching paths, merging paths - /// (diamond patterns), and multiple routes from roots to targets. - fun walk_from_node( - current: address, - target: address, - visited: &mut vector
, - current_power: u64, - current_depth: u64, - processed_count: &mut u64 - ): u64 { - // Early terminations that don't consume processing budget - if (current_depth >= MAX_PATH_DEPTH) return 0; - if (vector::contains(visited, ¤t)) return 0; - if (!vouch::is_init(current)) return 0; - if (current_power < 2) return 0; - if (current == target) return current_power; - - // Budget check and consumption - if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; - *processed_count = *processed_count + 1; - - let (neighbors, _) = vouch::get_given_vouches(current); - let neighbor_count = vector::length(&neighbors); - - if (neighbor_count == 0) return 0; - - let total_score = 0; - - // Direct connection check - if (vector::contains(&neighbors, &target) && current_depth + 1 < MAX_PATH_DEPTH) { - total_score = total_score + (current_power / 2); - }; - - let next_power = current_power / 2; - - // Special case for root-to-root vouching - 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) - ) { - vector::push_back(visited, current); - return next_power - }; - - // Add current to visited and explore neighbors - vector::push_back(visited, current); - let next_depth = current_depth + 1; - - // CRITICAL: Limit number of neighbors explored to prevent exponential explosion - let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; - let neighbors_explored = 0; - let i = 0; - - while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { - if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; - - let neighbor = *vector::borrow(&neighbors, i); - if (!vector::contains(visited, &neighbor) && neighbor != target) { - if (!root_of_trust::is_root_at_registry(@diem_framework, neighbor)) { - let visited_copy = *visited; - let path_score = walk_from_node( - neighbor, - target, - &mut visited_copy, - next_power, - next_depth, - processed_count - ); - total_score = total_score + path_score; - neighbors_explored = neighbors_explored + 1; - }; - }; - i = i + 1; - }; - total_score - } - /// Mark a user's trust score as stale, propagating the staleness to impacted downstream accounts. /// This function performs a controlled graph traversal to identify all accounts that may /// need to have their trust scores recalculated due to changes in the vouch graph. From 668a0a071a02e8559876da0be25c8ff887c7555b Mon Sep 17 00:00:00 2001 From: Crispin Accelerando Date: Fri, 23 May 2025 14:41:21 -0400 Subject: [PATCH 22/25] comments --- .../ol_sources/vouch_lib/page_rank_lazy.move | 118 +++++++----------- 1 file changed, 43 insertions(+), 75 deletions(-) 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 708dc6d09..021bbf64c 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,39 +16,34 @@ 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 //////// - /// Maximum score that can be assigned to a single vouch. - /// This provides an upper bound on direct trust from a single source. + /// 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 during recursive graph traversal. - /// Limits the total number of nodes processed in a single traversal. + /// Maximum number of nodes processed in a single traversal (prevents stack overflow). const MAX_PROCESSED_ADDRESSES: u64 = 10_000; - /// Maximum depth for path traversal in the trust graph. - /// This limits how far the algorithm will search from a root node. + /// Maximum allowed depth for trust graph traversal. const MAX_PATH_DEPTH: u64 = 5; - /// Per-user trust record - each user stores their own trust data - /// This resource tracks a user's cached trust score and staleness state. + /// Stores a user's trust score and its staleness state. struct UserTrustRecord has key, drop { - /// Cached trust score - computed by traversing the trust graph + /// Last computed trust score for this user. cached_score: u64, - /// When the score was last computed (timestamp in seconds) + /// Timestamp (seconds) when the score was last computed. score_computed_at_timestamp: u64, - /// Whether this node's trust data is stale and needs recalculation - /// Set to true when the trust graph changes in a way that affects this user + /// 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); @@ -62,8 +57,8 @@ module ol_framework::page_rank_lazy { } - /// Calculate or retrieve cached trust score for an address. - /// Returns the cached score if it's valid, or recalculates if stale. + /// 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 @@ -88,13 +83,9 @@ module ol_framework::page_rank_lazy { set_score(addr) } - /// Always calculate and update the trust score for an address. - /// This function: - /// 1. Gets the current roots of trust - /// 2. Traverses the graph to compute the score using our page rank algorithm - /// 3. Updates the user's cached score and marks it as fresh - /// - /// This is an expensive operation that should be used judiciously. + /// 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)); @@ -112,16 +103,8 @@ module ol_framework::page_rank_lazy { score } - /// REVERSE WALK EXPERIMENT: Traverse graph by walking backwards from target to roots of trust. - /// Instead of walking from all roots outward to a target user (using given_vouches), this - /// experimental approach walks from a target user backwards toward the roots of trust by - /// accessing received_vouches. This should reduce processing budget by avoiding dead-end paths. - /// - /// The algorithm: - /// 1. Starts from the target user - /// 2. Walks backwards through the vouch graph using received_vouches - /// 3. Accumulates trust score when reaching any root of trust - /// 4. Avoids exploring dead-end paths that don't lead to roots + /// Traverses the trust graph from the target user back to the roots of trust. + /// Uses received vouches to avoid dead-end paths and reduce processing. fun traverse_graph( roots: &vector
, target: address, @@ -136,8 +119,7 @@ module ol_framework::page_rank_lazy { ) } - /// REVERSE WALK EXPERIMENT: Traverse graph by walking backwards from target to roots of trust. - /// This version returns additional statistics: (score, max_depth_reached, accounts_processed) + /// Like `traverse_graph`, but also returns statistics: (score, max_depth, processed_count). fun traverse_graph_with_stats( roots: &vector
, target: address, @@ -154,18 +136,12 @@ module ol_framework::page_rank_lazy { (score, max_depth_reached, processed_count) } - /// REVERSE WALK EXPERIMENT: Walk backwards from target toward roots of trust. - /// This experimental function implements the reverse walk algorithm that starts from the target user - /// and walks backwards through the vouch graph using received_vouches to find paths to any roots of trust. - /// - /// Key differences from forward walk: - /// 1. Starts from target instead of iterating through all roots - /// 2. Uses get_received_vouches() instead of get_given_vouches() to traverse backwards - /// 3. Accumulates score when reaching any root in the roots vector - /// 4. Avoids exploring dead-end paths that don't lead to roots - /// 5. Maintains the same trust decay and cycle detection principles - /// - /// This should reduce processing budget by avoiding dead-end exploration. + /// 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, roots: &vector
, @@ -238,28 +214,22 @@ module ol_framework::page_rank_lazy { total_score } - /// Mark a user's trust score as stale, propagating the staleness to impacted downstream accounts. - /// This function performs a controlled graph traversal to identify all accounts that may - /// need to have their trust scores recalculated due to changes in the vouch graph. - /// - /// Uses cycle detection and a maximum node limit to prevent infinite recursion or DOS attacks. + /// 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 and implements optimizations to reduce - /// the number of nodes processed: - /// - /// 1. Cycle detection to avoid revisiting nodes - /// 2. Process limit to prevent excessive recursion - /// 3. Efficient traversal that prioritizes direct dependencies + /// 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. @@ -314,9 +284,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)); @@ -326,7 +295,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); @@ -334,13 +303,9 @@ module ol_framework::page_rank_lazy { } #[view] - /// Calculates a fresh trust score without updating the cache. - /// This is an expensive operation that traverses the entire relevant trust graph. - /// - /// WARNING: This function is provided for diagnostic and testing purposes. - /// In production, use get_trust_score() or get_cached_score() instead. - /// - /// Returns: (score, max_depth_reached, accounts_processed) + /// 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 @@ -352,7 +317,7 @@ module ol_framework::page_rank_lazy { } #[view] - // check if it's stale + /// 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); @@ -360,13 +325,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 /////// + /// 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. #[test_only] public fun setup_mock_trust_network( admin: &signer, From c206e0901d12443b3dd8538f728e438497b87d09 Mon Sep 17 00:00:00 2001 From: Mariella De Tempo Date: Fri, 23 May 2025 15:22:07 -0400 Subject: [PATCH 23/25] cleanup and add calculate_score_depth --- .../ol_sources/vouch_lib/page_rank_lazy.move | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) 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 021bbf64c..02c15d9e0 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 @@ -93,7 +93,12 @@ module ol_framework::page_rank_lazy { // 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 score = traverse_graph(&roots, addr); + 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; @@ -103,39 +108,6 @@ module ol_framework::page_rank_lazy { score } - /// Traverses the trust graph from the target user back to the roots of trust. - /// Uses received vouches to avoid dead-end paths and reduce processing. - fun traverse_graph( - roots: &vector
, - target: address, - ): u64 { - let processed_count: u64 = 0; - let max_depth_reached: u64 = 0; - let visited = vector::empty
(); - - // Start the reverse walk from the target and extract just the score - walk_backwards_from_target_with_stats( - target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached - ) - } - - /// Like `traverse_graph`, but also returns statistics: (score, max_depth, processed_count). - fun traverse_graph_with_stats( - roots: &vector
, - target: address, - ): (u64, u64, u64) { - let processed_count: u64 = 0; - let max_depth_reached: u64 = 0; - let visited = vector::empty
(); - - // Start the reverse walk from the target - let score = walk_backwards_from_target_with_stats( - target, roots, &mut visited, 2 * MAX_VOUCH_SCORE, 0, &mut processed_count, &mut max_depth_reached - ); - - (score, max_depth_reached, processed_count) - } - /// Walks backwards from the target toward roots of trust, accumulating trust score. /// - Starts from the target user. /// - Uses received vouches for traversal. @@ -149,7 +121,8 @@ module ol_framework::page_rank_lazy { current_power: u64, current_depth: u64, processed_count: &mut u64, - max_depth_reached: &mut u64 + max_depth_reached: &mut u64, + max_depth: u64 ): u64 { // Track maximum depth reached if (current_depth > *max_depth_reached) { @@ -157,7 +130,7 @@ module ol_framework::page_rank_lazy { }; // Early terminations that don't consume processing budget - if (current_depth >= MAX_PATH_DEPTH) return 0; + 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; @@ -184,17 +157,13 @@ module ol_framework::page_rank_lazy { // Add current to visited and explore received vouches (backwards) vector::push_back(visited, current); - // CRITICAL: Limit number of neighbors explored to prevent exponential explosion - let max_neighbors_to_explore = if (current_depth < 2) { 10 } else { 5 }; - let neighbors_explored = 0; let i = 0; - while (i < neighbor_count && neighbors_explored < max_neighbors_to_explore) { - if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; + while (i < neighbor_count) { + if (*processed_count >= MAX_PROCESSED_ADDRESSES) break; let neighbor = *vector::borrow(&received_from, i); if (!vector::contains(visited, &neighbor)) { - // Create a copy of visited for this path let visited_copy = *visited; let path_score = walk_backwards_from_target_with_stats( neighbor, @@ -203,10 +172,10 @@ module ol_framework::page_rank_lazy { next_power, next_depth, processed_count, - max_depth_reached + max_depth_reached, + max_depth ); total_score = total_score + path_score; - neighbors_explored = neighbors_explored + 1; }; i = i + 1; }; @@ -271,7 +240,7 @@ module ol_framework::page_rank_lazy { let len = vector::length(&outgoing_vouches); while (i < len) { // Check again if we've hit the processing limit - if (*processed_count >= MAX_PROCESSED_ADDRESSES - 5) break; + if (*processed_count >= MAX_PROCESSED_ADDRESSES) break; let each_vouchee = vector::borrow(&outgoing_vouches, i); // Pass the same mutable reference to processed_count. @@ -312,8 +281,29 @@ module ol_framework::page_rank_lazy { // 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 (score, max_depth, processed_count) = traverse_graph_with_stats(&roots, addr); - (score, max_depth, processed_count) + 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] @@ -331,11 +321,10 @@ module ol_framework::page_rank_lazy { } //////// TEST HELPERS /////// - - /// 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. #[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, From eda1373a35b0b8538bc77876242223f3b6ef8d4a Mon Sep 17 00:00:00 2001 From: Rupert Von Cony Date: Fri, 23 May 2025 15:55:19 -0400 Subject: [PATCH 24/25] aborts if hits limit --- .../sources/ol_sources/vouch_lib/page_rank_lazy.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 02c15d9e0..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 @@ -141,7 +141,7 @@ module ol_framework::page_rank_lazy { }; // Budget check and consumption - if (*processed_count >= MAX_PROCESSED_ADDRESSES) return 0; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); *processed_count = *processed_count + 1; // Get who vouched FOR this current user (backwards direction) @@ -160,7 +160,7 @@ module ol_framework::page_rank_lazy { let i = 0; while (i < neighbor_count) { - if (*processed_count >= MAX_PROCESSED_ADDRESSES) break; + assert!(*processed_count < MAX_PROCESSED_ADDRESSES, error::invalid_state(EMAX_PROCESSED_ADDRESSES)); let neighbor = *vector::borrow(&received_from, i); if (!vector::contains(visited, &neighbor)) { From 7d2e99c9fe53770e25bf6737beb570e0cfbd33a6 Mon Sep 17 00:00:00 2001 From: Sofi Hart Date: Fri, 23 May 2025 15:55:48 -0400 Subject: [PATCH 25/25] add benchmarks --- .../ol_sources/vouch_lib/benchmarks.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 framework/libra-framework/sources/ol_sources/vouch_lib/benchmarks.md 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 | - | - |