diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp
index 8f2255e134..9359b8271c 100644
--- a/libraries/chain/block_header_state.cpp
+++ b/libraries/chain/block_header_state.cpp
@@ -24,7 +24,8 @@ block_header_state_core block_header_state_core::next(qc_info_t incoming) const
    }
 
    EOS_ASSERT(incoming.last_qc_block_num > this->last_qc_block_num, block_validate_exception,
-              "new last_qc_block_num must be greater than old last_qc_block_num");
+              "new last_qc_block_num ${new} must be greater than old last_qc_block_num ${old}",
+              ("new", incoming.last_qc_block_num)("old", this->last_qc_block_num));
 
    auto old_last_qc_block_num            = this->last_qc_block_num;
    auto old_final_on_strong_qc_block_num = this->final_on_strong_qc_block_num;
@@ -117,9 +118,8 @@ block_header_state block_header_state::next(block_header_state_input& input) con
    result.active_finalizer_policy = active_finalizer_policy;
 
    // [greg todo] correct support for new finalizer_policy activation using finalizer_policies map
-
-   if (input.new_finalizer_policy)
-      ++input.new_finalizer_policy->generation;
+   //   if (input.new_finalizer_policy)
+   //      ++input.new_finalizer_policy->generation;
 
 
    // add IF block header extension
@@ -131,7 +131,9 @@ block_header_state block_header_state::next(block_header_state_input& input) con
    instant_finality_extension new_if_ext {if_ext.qc_info,
                                           std::move(input.new_finalizer_policy),
                                           std::move(input.new_proposer_policy)};
-   if (input.qc_info)
+   if (input.validating)
+      new_if_ext.qc_info = input.qc_info;
+   else if (input.qc_info)
       new_if_ext.qc_info = *input.qc_info;
 
    emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext));
@@ -196,7 +198,7 @@ block_header_state block_header_state::next(const signed_block_header& h, const
 
    block_header_state_input bhs_input{
       bb_input,      h.transaction_mroot, h.action_mroot, if_ext.new_proposer_policy, if_ext.new_finalizer_policy,
-      if_ext.qc_info};
+      if_ext.qc_info, true};
 
    return next(bhs_input);
 }
diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp
index a1cfd65670..a93f6ae136 100644
--- a/libraries/chain/block_state.cpp
+++ b/libraries/chain/block_state.cpp
@@ -9,12 +9,18 @@ block_state::block_state(const block_header_state& prev, signed_block_ptr b, con
                          const validator_t& validator, bool skip_validate_signee)
    : block_header_state(prev.next(*b, pfs, validator))
    , block(std::move(b))
+   , strong_digest(compute_finalizer_digest())
+   , weak_digest(compute_finalizer_digest())
+   , pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold)
 {}
 
 block_state::block_state(const block_header_state& bhs, deque<transaction_metadata_ptr>&& trx_metas,
                          deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc)
    : block_header_state(bhs)
    , block(std::make_shared<signed_block>(signed_block_header{bhs.header})) // [greg todo] do we need signatures?
+   , strong_digest(compute_finalizer_digest())
+   , weak_digest(compute_finalizer_digest())
+   , pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold)
    , pub_keys_recovered(true) // probably not needed
    , cached_trxs(std::move(trx_metas))
 {
@@ -29,6 +35,7 @@ block_state::block_state(const block_header_state& bhs, deque<transaction_metada
 block_state::block_state(const block_state_legacy& bsp) {
    block_header_state::id = bsp.id();
    header = bsp.header;
+   core.last_final_block_num = bsp.block_num();
    activated_protocol_features = bsp.activated_protocol_features;
    std::optional<block_header_extension> ext = bsp.block->extract_header_extension(instant_finality_extension::extension_id());
    assert(ext); // required by current transition mechanism
@@ -58,7 +65,7 @@ void block_state::set_trxs_metas( deque<transaction_metadata_ptr>&& trxs_metas,
 }
 
 // Called from net threads
-bool block_state::aggregate_vote(const hs_vote_message& vote) {
+std::pair<bool, std::optional<uint32_t>> block_state::aggregate_vote(const hs_vote_message& vote) {
    const auto& finalizers = active_finalizer_policy->finalizers;
    auto it = std::find_if(finalizers.begin(),
                           finalizers.end(),
@@ -67,15 +74,16 @@ bool block_state::aggregate_vote(const hs_vote_message& vote) {
    if (it != finalizers.end()) {
       auto index = std::distance(finalizers.begin(), it);
       const digest_type& digest = vote.strong ? strong_digest : weak_digest;
-      return pending_qc.add_vote(vote.strong,
+      auto [valid, strong] = pending_qc.add_vote(vote.strong,
 #warning TODO change to use std::span if possible
                                  std::vector<uint8_t>{digest.data(), digest.data() + digest.data_size()},
                                  index,
                                  vote.finalizer_key,
                                  vote.sig);
+      return {valid, strong ? core.final_on_strong_qc_block_num : std::optional<uint32_t>{}};
    } else {
       wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) );
-      return false;
+      return {false, {}};
    }
 }
    
diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp
index edc929fa40..9401331a82 100644
--- a/libraries/chain/controller.cpp
+++ b/libraries/chain/controller.cpp
@@ -302,16 +302,19 @@ struct block_data_t {
    }
 
     // called from net thread
-	 bool aggregate_vote(const hs_vote_message& vote) {
+	 std::pair<bool, std::optional<uint32_t>> aggregate_vote(const hs_vote_message& vote) {
 	    return std::visit(
-	       overloaded{[](const block_data_legacy_t&) {
+	       overloaded{[](const block_data_legacy_t&) -> std::pair<bool, std::optional<uint32_t>> {
                         // We can be late in switching to Instant Finality
                         // and receive votes from those already having switched.
-	                     return false; },
-	                  [&](const block_data_new_t& bd) {
+	                     return {false, {}}; },
+	                  [&](const block_data_new_t& bd) -> std::pair<bool, std::optional<uint32_t>> {
 	                     auto bsp = bd.fork_db.get_block(vote.proposal_id);
-	                     return bsp && bsp->aggregate_vote(vote); }
-                    },
+	                     if (bsp) {
+	                        return bsp->aggregate_vote(vote);
+	                     }
+	                     return {false, {}};
+                    }},
 	       v);
 	 }
 
@@ -857,7 +860,9 @@ struct building_block {
 
    assembled_block assemble_block(boost::asio::io_context& ioc,
                                   const protocol_feature_set& pfs,
-                                  const block_data_t& block_data) {
+                                  const block_data_t& block_data,
+                                  bool validating,
+                                  std::optional<qc_info_t> validating_qc_info) {
       digests_t& action_receipts = action_receipt_digests();
       return std::visit(
          overloaded{
@@ -913,13 +918,17 @@ struct building_block {
                // find most recent ancestor block that has a QC by traversing fork db
                // branch from parent
                std::optional<qc_data_t> qc_data;
-               auto branch = fork_db.fetch_branch(parent_id());
-               for( auto it = branch.begin(); it != branch.end(); ++it ) {
-                  auto qc = (*it)->get_best_qc();
-                  if( qc ) {
-                     EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception, "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) );
-                     qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }};
-                     break;
+               if (!validating) {
+                  auto branch = fork_db.fetch_branch(parent_id());
+                  for( auto it = branch.begin(); it != branch.end(); ++it ) {
+                     auto qc = (*it)->get_best_qc();
+                     if( qc ) {
+                        EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception,
+                                    "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})",
+                                    ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) );
+                        qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }};
+                        break;
+                     }
                   }
                }
 
@@ -930,9 +939,17 @@ struct building_block {
                   .new_protocol_feature_activations = new_protocol_feature_activations()
                };
 
+               std::optional<qc_info_t> qc_info;
+               if (validating) {
+                  qc_info = validating_qc_info;
+               } else if (qc_data) {
+                  qc_info = qc_data->qc_info;
+               }
                block_header_state_input bhs_input{
-                  bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy),
-                  qc_data ? qc_data->qc_info : std::optional<qc_info_t>{} };
+                  bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy),
+                  std::move(bb.new_finalizer_policy),
+                  qc_info, validating
+               };
 
                assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input),
                                                       std::move(bb.pending_trx_metas), std::move(bb.pending_trx_receipts),
@@ -2682,7 +2699,7 @@ struct controller_impl {
       guard_pending.cancel();
    } /// start_block
 
-   void finish_block()
+   void finish_block(bool validating = false, std::optional<qc_info_t> validating_qc_info = {})
    {
       EOS_ASSERT( pending, block_validate_exception, "it is not valid to finalize when there is no pending block");
       EOS_ASSERT( std::holds_alternative<building_block>(pending->_block_stage), block_validate_exception, "already called finish_block");
@@ -2705,8 +2722,8 @@ struct controller_impl {
             );
          resource_limits.process_block_usage(bb.block_num());
 
-         auto assembled_block =
-            bb.assemble_block(thread_pool.get_executor(), protocol_features.get_protocol_feature_set(), block_data);
+         auto assembled_block = bb.assemble_block(thread_pool.get_executor(),
+            protocol_features.get_protocol_feature_set(), block_data, validating, validating_qc_info);
 
          // Update TaPoS table:
          create_block_summary(  assembled_block.id() );
@@ -2875,6 +2892,23 @@ struct controller_impl {
       EOS_REPORT( "new_producers", b.new_producers, ab.new_producers )
       EOS_REPORT( "header_extensions", b.header_extensions, ab.header_extensions )
 
+      if (b.header_extensions != ab.header_extensions) {
+         {
+            flat_multimap<uint16_t, block_header_extension> bheader_exts = b.validate_and_extract_header_extensions();
+            if (bheader_exts.count(instant_finality_extension::extension_id())) {
+               const auto& if_extension =
+                       std::get<instant_finality_extension>(bheader_exts.lower_bound(instant_finality_extension::extension_id())->second);
+               elog("b  if: ${i}", ("i", if_extension));
+            }
+         }
+         flat_multimap<uint16_t, block_header_extension> abheader_exts = ab.validate_and_extract_header_extensions();
+         if (abheader_exts.count(instant_finality_extension::extension_id())) {
+            const auto& if_extension =
+                    std::get<instant_finality_extension>(abheader_exts.lower_bound(instant_finality_extension::extension_id())->second);
+            elog("ab if: ${i}", ("i", if_extension));
+         }
+      }
+
 #undef EOS_REPORT
    }
 
@@ -2968,7 +3002,13 @@ struct controller_impl {
                              ("lhs", r)("rhs", static_cast<const transaction_receipt_header&>(receipt)));
                }
 
-               finish_block();
+               std::optional<qc_info_t> qc_info;
+               auto exts = b->validate_and_extract_header_extensions();
+               if (auto if_entry = exts.lower_bound(instant_finality_extension::extension_id()); if_entry != exts.end()) {
+                  auto& if_ext   = std::get<instant_finality_extension>(if_entry->second);
+                  qc_info = if_ext.qc_info;
+               }
+               finish_block(true, qc_info);
 
                auto& ab = std::get<assembled_block>(pending->_block_stage);
 
@@ -3016,22 +3056,31 @@ struct controller_impl {
 
       // A vote is created and signed by each finalizer configured on the node that
       // in active finalizer policy
+      bool found = false;
+      // TODO: remove dlog statements
+      dlog( "active finalizers ${n}, threshold ${t}",
+         ("n", bsp->active_finalizer_policy->finalizers.size())("t", bsp->active_finalizer_policy->threshold));
       for (const auto& f: bsp->active_finalizer_policy->finalizers) {
          auto it = node_finalizer_keys.find( f.public_key );
          if( it != node_finalizer_keys.end() ) {
+            found = true;
+            dlog("finalizer used: ${f}", ("f", f.public_key.to_string()));
             const auto& private_key = it->second;
             const auto& digest = bsp->compute_finalizer_digest();
 
             auto sig =  private_key.sign(std::vector<uint8_t>(digest.data(), digest.data() + digest.data_size()));
 
             // construct the vote message
-            hs_vote_message vote{ bsp->id(), strong, private_key.get_public_key(), sig };
+            hs_vote_message vote{ bsp->id(), strong, f.public_key, sig };
 
             // net plugin subscribed this signal. it will broadcast the vote message
             // on receiving the signal
             emit( self.voted_block, vote );
          }
       }
+      if (!found) {
+         dlog("No finalizer found on node for key, we have ${n} finalizers configured", ("n", node_finalizer_keys.size()));
+      }
    }
 
    // thread safe, expected to be called from thread other than the main thread
@@ -3088,9 +3137,7 @@ struct controller_impl {
       );
 
       EOS_ASSERT( id == bsp->id(), block_validate_exception,
-                  "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) );
-
-      create_and_send_vote_msg(bsp);
+                  "provided id ${id} does not match calculated block id ${bid}", ("id", id)("bid", bsp->id()) );
 
       // integrate the received QC into the claimed block
       integrate_received_qc_to_block(id, b);
@@ -3179,6 +3226,9 @@ struct controller_impl {
 
          block_data.apply<void>(do_push);
 
+         if constexpr (std::is_same_v<block_state_ptr, typename std::decay_t<decltype(bsp)>>)
+            create_and_send_vote_msg(bsp);
+
       } FC_LOG_AND_RETHROW( )
    }
 
@@ -4266,7 +4316,11 @@ void controller::get_finalizer_state( finalizer_state& fs ) const {
 
 // called from net threads
 bool controller::process_vote_message( const hs_vote_message& vote ) {
-   return my->block_data.aggregate_vote(vote);
+   auto [valid, new_lib] = my->block_data.aggregate_vote(vote);
+   if (new_lib) {
+      my->if_irreversible_block_num = *new_lib;
+   }
+   return valid;
 };
 
 const producer_authority_schedule& controller::active_producers()const {
diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp
index 200331842e..c22b046620 100644
--- a/libraries/chain/hotstuff/hotstuff.cpp
+++ b/libraries/chain/hotstuff/hotstuff.cpp
@@ -87,6 +87,9 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& pro
    size_t weak   = num_weak();
    size_t strong = num_strong();
 
+   // TODO: remove dlog statement
+   dlog( "strong ${n}, q ${q}", ("n", strong)("q", _quorum));
+
    switch (_state) {
    case state_t::unrestricted:
    case state_t::restricted:
@@ -146,12 +149,13 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& propo
    return true;
 }
 
-// thread safe
-bool pending_quorum_certificate::add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
-                                          const bls_public_key& pubkey, const bls_signature& sig) {
+// thread safe, <valid, strong>
+std::pair<bool, bool> pending_quorum_certificate::add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
+                                                           const bls_public_key& pubkey, const bls_signature& sig) {
    std::lock_guard g(*_mtx);
-   return strong ? add_strong_vote(proposal_digest, index, pubkey, sig)
-                 : add_weak_vote(proposal_digest, index, pubkey, sig);
+   bool valid = strong ? add_strong_vote(proposal_digest, index, pubkey, sig)
+                       : add_weak_vote(proposal_digest, index, pubkey, sig);
+   return {valid, _state == state_t::strong};
 }
 
 // thread safe
diff --git a/libraries/chain/hotstuff/qc_chain.cpp b/libraries/chain/hotstuff/qc_chain.cpp
index 8315901810..e15af7729c 100644
--- a/libraries/chain/hotstuff/qc_chain.cpp
+++ b/libraries/chain/hotstuff/qc_chain.cpp
@@ -360,7 +360,7 @@ namespace eosio::chain {
          for (size_t i=0; i<finalizers.size(); ++i) {
             if (finalizers[i].public_key == vote.finalizer_key) {
                if (_current_qc.add_vote(vote.strong, std::vector<uint8_t>(digest.data(), digest.data() + 32),
-                                        i, vote.finalizer_key, vote.sig)) {
+                                        i, vote.finalizer_key, vote.sig).first) {
                   // fc_tlog(_logger, " === update bitset ${value} ${finalizer_key}",
                   //         ("value", _current_qc.get_active_finalizers_string())("finalizer_key", vote.finalizer_key));
                   if (_current_qc.is_quorum_met()) {
diff --git a/libraries/chain/hotstuff/test/hotstuff_tools.cpp b/libraries/chain/hotstuff/test/hotstuff_tools.cpp
index 1ecb2bbec4..a5b62ffda0 100644
--- a/libraries/chain/hotstuff/test/hotstuff_tools.cpp
+++ b/libraries/chain/hotstuff/test/hotstuff_tools.cpp
@@ -100,11 +100,11 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
       pubkey.push_back(k.get_public_key());
 
    auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
-      return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest));
+      return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest)).first;
    };
 
    auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
-      return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest));
+      return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest)).first;
    };
 
    {
diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp
index 138a847f3a..48253149bc 100644
--- a/libraries/chain/include/eosio/chain/block_header_state.hpp
+++ b/libraries/chain/include/eosio/chain/block_header_state.hpp
@@ -33,6 +33,7 @@ struct block_header_state_input : public building_block_input {
    std::optional<finalizer_policy>   new_finalizer_policy; // Comes from building_block::new_finalizer_policy
    std::optional<qc_info_t>          qc_info;              // Comes from traversing branch from parent and calling get_best_qc()
                                                            // assert(qc->block_num <= num_from_id(previous));
+   bool                              validating = false;
 };
 
 struct block_header_state_core {
diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp
index a95fc61a7a..8c0b633107 100644
--- a/libraries/chain/include/eosio/chain/block_state.hpp
+++ b/libraries/chain/include/eosio/chain/block_state.hpp
@@ -38,7 +38,7 @@ struct block_state : public block_header_state {     // block_header_state provi
    deque<transaction_metadata_ptr>     extract_trxs_metas();
    void                                set_trxs_metas(deque<transaction_metadata_ptr>&& trxs_metas, bool keys_recovered);
    const deque<transaction_metadata_ptr>& trxs_metas()  const { return cached_trxs; }
-   bool                                aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc
+   std::pair<bool, std::optional<uint32_t>> aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc
 
    using bhs_t  = block_header_state;
    using bhsp_t = block_header_state_ptr;
diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
index d349597f7a..70efe9ffc1 100644
--- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
+++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
@@ -199,11 +199,11 @@ namespace eosio::chain {
       void reset(const fc::sha256& proposal_id, const digest_type& proposal_digest, size_t num_finalizers, size_t quorum);
 
       // thread safe
-      bool add_vote(bool strong,
-                    const std::vector<uint8_t>& proposal_digest,
-                    size_t index,
-                    const bls_public_key& pubkey,
-                    const bls_signature& sig);
+      std::pair<bool, bool> add_vote(bool strong,
+                                     const std::vector<uint8_t>&proposal_digest,
+                                     size_t index,
+                                     const bls_public_key&pubkey,
+                                     const bls_signature&sig);
 
       state_t state()  const { std::lock_guard g(*_mtx); return _state; };
       valid_quorum_certificate to_valid_quorum_certificate() const;
diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp
index 28ad30258c..e28df4ae41 100644
--- a/unittests/block_state_tests.cpp
+++ b/unittests/block_state_tests.cpp
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
          bool strong = (i % 2 == 0); // alternate strong and weak
          auto sig = strong ? private_key[i].sign(strong_digest_data) : private_key[i].sign(weak_digest_data);
          hs_vote_message vote{ block_id, strong, public_key[i], sig };
-         BOOST_REQUIRE(bsp->aggregate_vote(vote));
+         BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
       }
    }
 
@@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
       bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
 
       hs_vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest_data) };
-      BOOST_REQUIRE(!bsp->aggregate_vote(vote));
+      BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
    }
 
    {  // duplicate votes 
@@ -69,8 +69,8 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
       bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
 
       hs_vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest_data) };
-      BOOST_REQUIRE(bsp->aggregate_vote(vote));
-      BOOST_REQUIRE(!bsp->aggregate_vote(vote));
+      BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
+      BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
    }
 
    {  // public key does not exit in finalizer set
@@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
       bls_public_key new_public_key{ new_private_key.get_public_key() };
 
       hs_vote_message vote {block_id, true, new_public_key, private_key[0].sign(strong_digest_data) };
-      BOOST_REQUIRE(!bsp->aggregate_vote(vote));
+      BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
    }
 } FC_LOG_AND_RETHROW();