This adds the most important updates to Graphene from BitShares. Most notably, https://github.com/bitshares/bitshares-core/issues/1506 Second most notably, it updates Peerplays' FC to be in sync with BitShares FC. This is a squash commit of several subcommits. The subcommit messages are reproduced below: Replace fc::uint128 with boost::multiprecision::uint128_t replace smart_ref with shared_ptr Fixes/Remove Unused Remove NTP time Remove old macro This macro is now in FC, so no need to define it here anymore Replaced fc::array with std::array Separate exception declaration and implementation Adapted to fc promise changes Fixes Add back in some of Peter's fixes that got lost in the cherry pick _hash endianness fixes Remove all uses of fc/smart_ref It's gone, can't use it anymore Replace improper static_variant operator overloads with comparators Fixes Remove boost::signals from build system; it's header-only so it's not listed in cmake anymore. Also remove some unused hashing code Impl. pack/unpack functions for extension class Ref #1506: Isolate chain/protocol to its own library Ref #1506: Add object_downcast_t Allows the more concise expression `object_downcast_t<xyz>` instead of the old `typename object_downcast<xyz>::type` Ref #1506: Move ID types from db to protocol The ID types, object_id and object_id_type, were defined in the db library, and the protocol library depends on db to get these types. Technically, the ID types are defined by the protocol and used by the database, and not vice versa. Therefore these types should be in the protocol library, and db should depend on protocol to get them. This commit makes it so. Ref #1506: Isolate chain/protocol to its own library Remove commented-out index code Wrap overlength line Remove unused key types Probably fix Docker build Fix build after rebase Ref #1506/#1737: Some requested changes Ref #1506/#1737: Macro-fy ID type definitions Define macros to fully de-boilerplate ID type definitions. Externalities: - Rename transaction_object -> transaction_history_object - Rename impl_asset_dynamic_data_type -> impl_asset_dynamic_data_object_type - Rename impl_asset_bitasset_data_type -> impl_asset_bitasset_data_object_type The first is to avoid a naming collision on transaction_id_type, and the other two are to maintain consistency with the naming of the other types. Ref #1506/#1737: Fix clean_name() Ref #1506/#1737: Oops Fix .gitignore Externalized serialization in protocol library Fix compile sets Delete a couple of ghost files that were in the tree but not part of the project (I accidentally added them to CMakeLists while merging, but they're broken and not part of the Peerplays code), and add several files that got dropped from the build during merge. General fixes Fix warnings, build issues, unused code, etc. Fix #1772 by decprecating cli_wallet -H More fixes Fix errors and warnings and generally coax it to build Fix test I'm pretty sure this didn't break from what I did... But I can't build the original code, so I can't tell. Anyways, this one now passes... Others still fail... Small fix Fix crash in auth checks Final fixes Last round of fixes following the rebase to Beatrice Rename project in CMakeLists.txt The CMakeLists.txt declared this project as BitShares and not Peerplays, which makes it confusing in IDEs. Rename it to be clear which project is open. Resolve #374 Replace all object refs in macros with IDs, and fix affected tests to look up objects by ID rather than using invalidated refs. A full audit of all tests should be performed to eliminate any further usage of invalidated object references. Resolve #373: Add object notifiers Various fixes Fixes to various issues, primarily reflections, that cropped up during merge conflict resolution Fix startup bug in Bookie plugin Bookie plugin was preventing the node from starting up because it registered its secondary indexes to create objects in its own primary indexes to track objects being created in other primary indexes, and did so during its `initialize()` step, which is to say, before the database was loaded from disk at startup. This caused the secondary indexes to create tracker objects when the observed indexes were loading objects from disk. This then caused a failure when these tracker indexes were later loaded from disk, and the first object IDs collided. This is fixed by refraining from defining secondary indexes until the `startup()` stage rather than the `initialize()` stage. Primary indexes are registered in `initialize()`, secondary indexes are registered in `startup()`. This also involved adding a new method, "add_secondary_index()", to `object_database`, as before there was no way to do this because you couldn't get a non-const index from a non-const database. I have no idea how this was working before I got here... Fix egenesis install Fixes after updates Rebase on updated develop branch and fix conflicts
463 lines
18 KiB
C++
463 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
|
*
|
|
* The MIT License
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include <graphene/protocol/transaction.hpp>
|
|
#include <graphene/protocol/block.hpp>
|
|
#include <graphene/protocol/exceptions.hpp>
|
|
#include <graphene/protocol/fee_schedule.hpp>
|
|
#include <graphene/protocol/pts_address.hpp>
|
|
|
|
#include <fc/io/raw.hpp>
|
|
#include <algorithm>
|
|
#include <fc/io/raw.hpp>
|
|
|
|
namespace graphene { namespace protocol {
|
|
|
|
digest_type processed_transaction::merkle_digest()const
|
|
{
|
|
digest_type::encoder enc;
|
|
fc::raw::pack( enc, *this );
|
|
return enc.result();
|
|
}
|
|
|
|
digest_type transaction::digest()const
|
|
{
|
|
digest_type::encoder enc;
|
|
fc::raw::pack( enc, *this );
|
|
return enc.result();
|
|
}
|
|
|
|
digest_type transaction::sig_digest( const chain_id_type& chain_id )const
|
|
{
|
|
digest_type::encoder enc;
|
|
fc::raw::pack( enc, chain_id );
|
|
fc::raw::pack( enc, *this );
|
|
return enc.result();
|
|
}
|
|
|
|
void transaction::validate() const
|
|
{
|
|
FC_ASSERT( operations.size() > 0, "A transaction must have at least one operation", ("trx",*this) );
|
|
for( const auto& op : operations )
|
|
operation_validate(op);
|
|
}
|
|
|
|
graphene::protocol::transaction_id_type graphene::protocol::transaction::id() const
|
|
{
|
|
auto h = digest();
|
|
transaction_id_type result;
|
|
memcpy(result._hash, h._hash, std::min(sizeof(result), sizeof(h)));
|
|
return result;
|
|
}
|
|
|
|
const signature_type& graphene::protocol::signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id)
|
|
{
|
|
digest_type h = sig_digest( chain_id );
|
|
signatures.push_back(key.sign_compact(h));
|
|
signees.clear(); // Clear signees since it may be inconsistent after added a new signature
|
|
return signatures.back();
|
|
}
|
|
|
|
signature_type graphene::protocol::signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id)const
|
|
{
|
|
digest_type::encoder enc;
|
|
fc::raw::pack( enc, chain_id );
|
|
fc::raw::pack( enc, *this );
|
|
return key.sign_compact(enc.result());
|
|
}
|
|
|
|
void transaction::set_expiration( fc::time_point_sec expiration_time )
|
|
{
|
|
expiration = expiration_time;
|
|
}
|
|
|
|
void transaction::set_reference_block( const block_id_type& reference_block )
|
|
{
|
|
ref_block_num = boost::endian::endian_reverse(reference_block._hash[0].value());
|
|
ref_block_prefix = reference_block._hash[1].value();
|
|
}
|
|
|
|
void transaction::get_required_authorities( flat_set<account_id_type>& active,
|
|
flat_set<account_id_type>& owner,
|
|
vector<authority>& other,
|
|
bool ignore_custom_operation_required_auths )const
|
|
{
|
|
for ( const auto& op : operations )
|
|
operation_get_required_authorities( op, active, owner, other, ignore_custom_operation_required_auths );
|
|
}
|
|
|
|
|
|
|
|
const flat_set<public_key_type> empty_keyset;
|
|
|
|
struct sign_state
|
|
{
|
|
/** returns true if we have a signature for this key or can
|
|
* produce a signature for this key, else returns false.
|
|
*/
|
|
bool signed_by( const public_key_type& k )
|
|
{
|
|
auto itr = provided_signatures.find(k);
|
|
if( itr == provided_signatures.end() )
|
|
{
|
|
auto pk = available_keys.find(k);
|
|
if( pk != available_keys.end() )
|
|
return provided_signatures[k] = true;
|
|
return false;
|
|
}
|
|
return itr->second = true;
|
|
}
|
|
|
|
optional<map<address,public_key_type>> available_address_sigs;
|
|
optional<map<address,public_key_type>> provided_address_sigs;
|
|
|
|
bool signed_by( const address& a ) {
|
|
if( !available_address_sigs ) {
|
|
available_address_sigs = std::map<address,public_key_type>();
|
|
provided_address_sigs = std::map<address,public_key_type>();
|
|
for( auto& item : available_keys ) {
|
|
(*available_address_sigs)[ address(pts_address(item, false, 56) ) ] = item;
|
|
(*available_address_sigs)[ address(pts_address(item, true, 56) ) ] = item;
|
|
(*available_address_sigs)[ address(pts_address(item, false, 0) ) ] = item;
|
|
(*available_address_sigs)[ address(pts_address(item, true, 0) ) ] = item;
|
|
(*available_address_sigs)[ address(item) ] = item;
|
|
}
|
|
for( auto& item : provided_signatures ) {
|
|
(*provided_address_sigs)[ address(pts_address(item.first, false, 56) ) ] = item.first;
|
|
(*provided_address_sigs)[ address(pts_address(item.first, true, 56) ) ] = item.first;
|
|
(*provided_address_sigs)[ address(pts_address(item.first, false, 0) ) ] = item.first;
|
|
(*provided_address_sigs)[ address(pts_address(item.first, true, 0) ) ] = item.first;
|
|
(*provided_address_sigs)[ address(item.first) ] = item.first;
|
|
}
|
|
}
|
|
auto itr = provided_address_sigs->find(a);
|
|
if( itr == provided_address_sigs->end() )
|
|
{
|
|
auto aitr = available_address_sigs->find(a);
|
|
if( aitr != available_address_sigs->end() ) {
|
|
auto pk = available_keys.find(aitr->second);
|
|
if( pk != available_keys.end() )
|
|
return provided_signatures[aitr->second] = true;
|
|
return false;
|
|
}
|
|
}
|
|
return provided_signatures[itr->second] = true;
|
|
}
|
|
|
|
bool check_authority( account_id_type id )
|
|
{
|
|
if( approved_by.find(id) != approved_by.end() ) return true;
|
|
return check_authority( get_active(id) );
|
|
}
|
|
|
|
/**
|
|
* Checks to see if we have signatures of the active authorites of
|
|
* the accounts specified in authority or the keys specified.
|
|
*/
|
|
bool check_authority( const authority* au, uint32_t depth = 0 )
|
|
{
|
|
if( au == nullptr ) return false;
|
|
const authority& auth = *au;
|
|
|
|
uint32_t total_weight = 0;
|
|
for( const auto& k : auth.key_auths )
|
|
if( signed_by( k.first ) )
|
|
{
|
|
total_weight += k.second;
|
|
if( total_weight >= auth.weight_threshold )
|
|
return true;
|
|
}
|
|
|
|
for( const auto& k : auth.address_auths )
|
|
if( signed_by( k.first ) )
|
|
{
|
|
total_weight += k.second;
|
|
if( total_weight >= auth.weight_threshold )
|
|
return true;
|
|
}
|
|
|
|
for( const auto& a : auth.account_auths )
|
|
{
|
|
if( approved_by.find(a.first) == approved_by.end() )
|
|
{
|
|
if( depth == max_recursion )
|
|
continue;
|
|
if( check_authority( get_active( a.first ), depth+1 ) )
|
|
{
|
|
approved_by.insert( a.first );
|
|
total_weight += a.second;
|
|
if( total_weight >= auth.weight_threshold )
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
total_weight += a.second;
|
|
if( total_weight >= auth.weight_threshold )
|
|
return true;
|
|
}
|
|
}
|
|
return total_weight >= auth.weight_threshold;
|
|
}
|
|
|
|
bool remove_unused_signatures()
|
|
{
|
|
vector<public_key_type> remove_sigs;
|
|
for( const auto& sig : provided_signatures )
|
|
if( !sig.second ) remove_sigs.push_back( sig.first );
|
|
|
|
for( auto& sig : remove_sigs )
|
|
provided_signatures.erase(sig);
|
|
|
|
return remove_sigs.size() != 0;
|
|
}
|
|
|
|
sign_state( const flat_set<public_key_type>& sigs,
|
|
const std::function<const authority*(account_id_type)>& a,
|
|
const flat_set<public_key_type>& keys = empty_keyset )
|
|
:get_active(a),available_keys(keys)
|
|
{
|
|
for( const auto& key : sigs )
|
|
provided_signatures[ key ] = false;
|
|
approved_by.insert( GRAPHENE_TEMP_ACCOUNT );
|
|
}
|
|
|
|
const std::function<const authority*(account_id_type)>& get_active;
|
|
const flat_set<public_key_type>& available_keys;
|
|
|
|
flat_map<public_key_type,bool> provided_signatures;
|
|
flat_set<account_id_type> approved_by;
|
|
uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH;
|
|
};
|
|
|
|
|
|
void verify_authority( const vector<operation>& ops, const flat_set<public_key_type>& sigs,
|
|
const std::function<const authority*(account_id_type)>& get_active,
|
|
const std::function<const authority*(account_id_type)>& get_owner,
|
|
const std::function<vector<authority>(account_id_type, const operation&)>& get_custom,
|
|
bool ignore_custom_operation_required_auths,
|
|
uint32_t max_recursion_depth,
|
|
bool allow_committee,
|
|
const flat_set<account_id_type>& active_aprovals,
|
|
const flat_set<account_id_type>& owner_approvals )
|
|
{ try {
|
|
flat_set<account_id_type> required_active;
|
|
flat_set<account_id_type> required_owner;
|
|
vector<authority> other;
|
|
flat_set<public_key_type> available_keys;
|
|
|
|
sign_state s(sigs,get_active, available_keys);
|
|
s.max_recursion = max_recursion_depth;
|
|
for( auto& id : active_aprovals )
|
|
s.approved_by.insert( id );
|
|
for( auto& id : owner_approvals )
|
|
s.approved_by.insert( id );
|
|
|
|
auto approved_by_custom_authority = [&s, &get_custom](
|
|
account_id_type account,
|
|
operation op ) mutable {
|
|
auto custom_auths = get_custom( account, op );
|
|
for( const auto& auth : custom_auths )
|
|
if( s.check_authority( &auth ) ) return true;
|
|
return false;
|
|
};
|
|
|
|
for( const auto& op : ops ) {
|
|
flat_set<account_id_type> operation_required_active;
|
|
operation_get_required_authorities( op, operation_required_active, required_owner, other,
|
|
ignore_custom_operation_required_auths );
|
|
|
|
auto itr = operation_required_active.begin();
|
|
while ( itr != operation_required_active.end() ) {
|
|
if ( approved_by_custom_authority( *itr, op ) )
|
|
itr = operation_required_active.erase( itr );
|
|
else
|
|
++itr;
|
|
}
|
|
|
|
required_active.insert( operation_required_active.begin(), operation_required_active.end() );
|
|
}
|
|
|
|
if( !allow_committee )
|
|
GRAPHENE_ASSERT( required_active.find(GRAPHENE_COMMITTEE_ACCOUNT) == required_active.end(),
|
|
invalid_committee_approval, "Committee account may only propose transactions" );
|
|
|
|
|
|
for( const auto& auth : other )
|
|
{
|
|
GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, "Missing Authority", ("auth",auth)("sigs",sigs) );
|
|
}
|
|
|
|
// fetch all of the top level authorities
|
|
for( auto id : required_active )
|
|
{
|
|
GRAPHENE_ASSERT( s.check_authority(id) ||
|
|
s.check_authority(get_owner(id)),
|
|
tx_missing_active_auth, "Missing Active Authority ${id}", ("id",id)("auth",*get_active(id))("owner",*get_owner(id)) );
|
|
}
|
|
|
|
for( auto id : required_owner )
|
|
{
|
|
GRAPHENE_ASSERT( owner_approvals.find(id) != owner_approvals.end() ||
|
|
s.check_authority(get_owner(id)),
|
|
tx_missing_owner_auth, "Missing Owner Authority ${id}", ("id",id)("auth",*get_owner(id)) );
|
|
}
|
|
|
|
GRAPHENE_ASSERT(
|
|
!s.remove_unused_signatures(),
|
|
tx_irrelevant_sig,
|
|
"Unnecessary signature(s) detected"
|
|
);
|
|
} FC_CAPTURE_AND_RETHROW( (ops)(sigs) ) }
|
|
|
|
|
|
const flat_set<public_key_type>& signed_transaction::get_signature_keys( const chain_id_type& chain_id )const
|
|
{ try {
|
|
// Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field.
|
|
// However, we don't pass in another chain ID so far, for better performance, we skip the check.
|
|
if( signees.empty() && !signatures.empty() )
|
|
{
|
|
auto d = sig_digest( chain_id );
|
|
flat_set<public_key_type> result;
|
|
for( const auto& sig : signatures )
|
|
{
|
|
GRAPHENE_ASSERT(
|
|
result.insert( fc::ecc::public_key(sig,d) ).second,
|
|
tx_duplicate_sig,
|
|
"Duplicate Signature detected" );
|
|
}
|
|
signees = std::move( result );
|
|
}
|
|
return signees;
|
|
} FC_CAPTURE_AND_RETHROW() }
|
|
|
|
|
|
set<public_key_type> signed_transaction::get_required_signatures(
|
|
const chain_id_type& chain_id,
|
|
const flat_set<public_key_type>& available_keys,
|
|
const std::function<const authority*(account_id_type)>& get_active,
|
|
const std::function<const authority*(account_id_type)>& get_owner,
|
|
const std::function<vector<authority>(account_id_type, const operation&)>& get_custom,
|
|
bool ignore_custom_operation_required_authorities,
|
|
uint32_t max_recursion_depth )const
|
|
{
|
|
flat_set<account_id_type> required_active;
|
|
flat_set<account_id_type> required_owner;
|
|
vector<authority> other;
|
|
|
|
const flat_set<public_key_type>& signature_keys = get_signature_keys( chain_id );
|
|
sign_state s( signature_keys, get_active, available_keys );
|
|
s.max_recursion = max_recursion_depth;
|
|
|
|
auto approved_by_custom_authority = [&s, &get_custom](
|
|
account_id_type account,
|
|
operation op ) mutable {
|
|
auto custom_auths = get_custom( account, op );
|
|
for( const auto& auth : custom_auths )
|
|
if( s.check_authority( &auth ) ) return true;
|
|
return false;
|
|
};
|
|
|
|
for( const auto& op : operations ) {
|
|
flat_set<account_id_type> operation_required_active;
|
|
operation_get_required_authorities( op, operation_required_active, required_owner, other, ignore_custom_operation_required_authorities );
|
|
|
|
auto itr = operation_required_active.begin();
|
|
while ( itr != operation_required_active.end() ) {
|
|
if ( approved_by_custom_authority( *itr, op ) )
|
|
itr = operation_required_active.erase( itr );
|
|
else
|
|
++itr;
|
|
}
|
|
|
|
required_active.insert( operation_required_active.begin(), operation_required_active.end() );
|
|
}
|
|
|
|
for (const auto& auth : other)
|
|
s.check_authority(&auth);
|
|
for( auto& owner : required_owner )
|
|
s.check_authority( get_owner( owner ) );
|
|
for( auto& active : required_active )
|
|
s.check_authority( active );
|
|
|
|
s.remove_unused_signatures();
|
|
|
|
set<public_key_type> result;
|
|
|
|
for( auto& provided_sig : s.provided_signatures )
|
|
if( available_keys.find( provided_sig.first ) != available_keys.end() )
|
|
result.insert( provided_sig.first );
|
|
|
|
return result;
|
|
}
|
|
|
|
set<public_key_type> signed_transaction::minimize_required_signatures(
|
|
const chain_id_type& chain_id,
|
|
const flat_set<public_key_type>& available_keys,
|
|
const std::function<const authority*(account_id_type)>& get_active,
|
|
const std::function<const authority*(account_id_type)>& get_owner,
|
|
const std::function<vector<authority>(account_id_type, const operation&)>& get_custom,
|
|
bool ignore_custom_operation_required_auths,
|
|
uint32_t max_recursion
|
|
) const
|
|
{
|
|
set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner, get_custom,
|
|
ignore_custom_operation_required_auths, max_recursion );
|
|
flat_set< public_key_type > result( s.begin(), s.end() );
|
|
|
|
for( const public_key_type& k : s )
|
|
{
|
|
result.erase( k );
|
|
try
|
|
{
|
|
graphene::protocol::verify_authority( operations, result, get_active, get_owner, get_custom,
|
|
ignore_custom_operation_required_auths, max_recursion );
|
|
continue; // element stays erased if verify_authority is ok
|
|
}
|
|
catch( const tx_missing_owner_auth& e ) {}
|
|
catch( const tx_missing_active_auth& e ) {}
|
|
catch( const tx_missing_other_auth& e ) {}
|
|
result.insert( k );
|
|
}
|
|
return set<public_key_type>( result.begin(), result.end() );
|
|
}
|
|
|
|
void signed_transaction::verify_authority(
|
|
const chain_id_type& chain_id,
|
|
const std::function<const authority*(account_id_type)>& get_active,
|
|
const std::function<const authority*(account_id_type)>& get_owner,
|
|
const std::function<vector<authority>(account_id_type, const operation&)>& get_custom,
|
|
bool ignore_custom_operation_required_auths,
|
|
uint32_t max_recursion )const
|
|
{ try {
|
|
graphene::protocol::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner,
|
|
get_custom, ignore_custom_operation_required_auths, max_recursion );
|
|
} FC_CAPTURE_AND_RETHROW( (*this) ) }
|
|
|
|
} } // graphene::protocol
|
|
|
|
GRAPHENE_EXTERNAL_SERIALIZATION(/*not extern*/, graphene::protocol::transaction)
|
|
GRAPHENE_EXTERNAL_SERIALIZATION(/*not extern*/, graphene::protocol::signed_transaction)
|
|
GRAPHENE_EXTERNAL_SERIALIZATION(/*not extern*/, graphene::protocol::processed_transaction)
|
|
|