[GUI] Implement transaction broadcasting

Still need to set expiration, so none of the transactions I broadcast
work yet... :( Sadly there is no testnet so I can't finish this. Oh well.
I'm sure it'll be much easier on Monday.
This commit is contained in:
Nathan Hourt 2015-07-31 17:56:22 -04:00
parent 02682e761c
commit 99d6450473
9 changed files with 68 additions and 18 deletions

View file

@ -564,7 +564,7 @@ namespace graphene { namespace app {
_app.p2p_node()->broadcast_transaction(trx); _app.p2p_node()->broadcast_transaction(trx);
} }
void network_broadcast_api::broadcast_transaction_with_callback( confirmation_callback cb, const signed_transaction& trx) void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const signed_transaction& trx)
{ {
trx.validate(); trx.validate();
_callbacks[trx.id()] = cb; _callbacks[trx.id()] = cb;
@ -741,7 +741,7 @@ namespace graphene { namespace app {
{ {
if( _account_subscriptions.size() ) if( _account_subscriptions.size() )
{ {
map<account_id_type, vector<variant> > broadcast_queue; map<account_id_type, vector<variant> > broadcast_queue;
for( const auto& obj : objs ) for( const auto& obj : objs )
{ {
auto relevant = get_relevant_accounts( obj ); auto relevant = get_relevant_accounts( obj );
@ -795,7 +795,7 @@ namespace graphene { namespace app {
void database_api::on_objects_changed(const vector<object_id_type>& ids) void database_api::on_objects_changed(const vector<object_id_type>& ids)
{ {
vector<object_id_type> my_objects; vector<object_id_type> my_objects;
map<account_id_type, vector<variant> > broadcast_queue; map<account_id_type, vector<variant> > broadcast_queue;
map< pair<asset_id_type, asset_id_type>, vector<variant> > market_broadcast_queue; map< pair<asset_id_type, asset_id_type>, vector<variant> > market_broadcast_queue;
for(auto id : ids) for(auto id : ids)
{ {
@ -888,7 +888,7 @@ namespace graphene { namespace app {
if(_market_subscriptions.size() == 0) if(_market_subscriptions.size() == 0)
return; return;
const auto& ops = _db.get_applied_operations(); const auto& ops = _db.get_applied_operations();
map< std::pair<asset_id_type,asset_id_type>, vector<pair<operation, operation_result>> > subscribed_markets_ops; map< std::pair<asset_id_type,asset_id_type>, vector<pair<operation, operation_result>> > subscribed_markets_ops;
for(const auto& op : ops) for(const auto& op : ops)

View file

@ -35,6 +35,9 @@ public:
m_account.name = name.toStdString(); m_account.name = name.toStdString();
} }
void setAccountObject(const account_object& obj); void setAccountObject(const account_object& obj);
const account_object& accountObject()const {
return m_account;
}
QString name()const { return QString::fromStdString(m_account.name); } QString name()const { return QString::fromStdString(m_account.name); }
QString memoKey()const; QString memoKey()const;

View file

@ -1,6 +1,7 @@
#include "ChainDataModel.hpp" #include "ChainDataModel.hpp"
#include "Balance.hpp" #include "Balance.hpp"
#include "Operations.hpp" #include "Operations.hpp"
#include "Transaction.hpp"
#include <graphene/app/api.hpp> #include <graphene/app/api.hpp>
#include <graphene/chain/protocol/protocol.hpp> #include <graphene/chain/protocol/protocol.hpp>
@ -26,11 +27,35 @@ void ChainDataModel::setDatabaseAPI(fc::api<database_api> dbapi) {
m_db_api = dbapi; m_db_api = dbapi;
m_rpc_thread->async([this] { m_rpc_thread->async([this] {
m_global_properties = m_db_api->get_global_properties(); m_global_properties = m_db_api->get_global_properties();
m_db_api->subscribe_to_objects([this](const variant& v) { m_global_properties = v.as<global_property_object>(); }, m_db_api->subscribe_to_objects([this](const variant& v) {
{m_global_properties.id}); m_global_properties = v.as<global_property_object>();
}, {m_global_properties.id});
m_dynamic_global_properties = m_db_api->get_dynamic_global_properties();
m_db_api->subscribe_to_objects([this](const variant& d) {
m_dynamic_global_properties = d.as<dynamic_global_property_object>();
}, {m_dynamic_global_properties.id});
}); });
} }
void ChainDataModel::setNetworkAPI(fc::api<network_broadcast_api> napi)
{
m_net_api = napi;
}
void ChainDataModel::broadcast(Transaction* transaction)
{
try {
m_net_api->broadcast_transaction_with_callback([transaction](const fc::variant&) {
transaction->setStatus(Transaction::Complete);
}, transaction->internalTransaction());
transaction->setStatus(Transaction::Pending);
} catch (const fc::exception& e) {
transaction->setStatus(Transaction::Failed);
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
}
}
Asset* ChainDataModel::getAsset(ObjectId id) Asset* ChainDataModel::getAsset(ObjectId id)
{ {
auto& by_id_idx = m_assets.get<by_id>(); auto& by_id_idx = m_assets.get<by_id>();

View file

@ -34,6 +34,7 @@ typedef multi_index_container<
> >
> account_multi_index_type; > account_multi_index_type;
class Transaction;
class ChainDataModel : public QObject { class ChainDataModel : public QObject {
Q_OBJECT Q_OBJECT
@ -52,8 +53,13 @@ public:
ChainDataModel(fc::thread& t, QObject* parent = nullptr); ChainDataModel(fc::thread& t, QObject* parent = nullptr);
void setDatabaseAPI(fc::api<graphene::app::database_api> dbapi); void setDatabaseAPI(fc::api<graphene::app::database_api> dbapi);
void setNetworkAPI(fc::api<graphene::app::network_broadcast_api> napi);
const graphene::chain::global_property_object& global_properties() const { return m_global_properties; } const graphene::chain::global_property_object& global_properties() const { return m_global_properties; }
const graphene::chain::dynamic_global_property_object& dynamic_global_properties() const { return m_dynamic_global_properties; }
public Q_SLOTS:
void broadcast(Transaction* transaction);
Q_SIGNALS: Q_SIGNALS:
void queueExecute(const std::function<void()>&); void queueExecute(const std::function<void()>&);
@ -63,8 +69,10 @@ private:
fc::thread* m_rpc_thread = nullptr; fc::thread* m_rpc_thread = nullptr;
std::string m_api_url; std::string m_api_url;
fc::api<graphene::app::database_api> m_db_api; fc::api<graphene::app::database_api> m_db_api;
fc::api<graphene::app::network_broadcast_api> m_net_api;
graphene::chain::global_property_object m_global_properties; graphene::chain::global_property_object m_global_properties;
graphene::chain::dynamic_global_property_object m_dynamic_global_properties;
ObjectId m_account_query_num = -1; ObjectId m_account_query_num = -1;
account_multi_index_type m_accounts; account_multi_index_type m_accounts;

View file

@ -64,10 +64,12 @@ void GrapheneApplication::start(QString apiurl, QString user, QString pass)
Q_EMIT loginFailed(); Q_EMIT loginFailed();
return; return;
} }
auto net_api = remote_api->network_broadcast();
ilog("connecting..."); ilog("connecting...");
queueExecute([=](){ queueExecute([=](){
m_model->setDatabaseAPI(db_api); m_model->setDatabaseAPI(db_api);
m_model->setNetworkAPI(net_api);
}); });
queueExecute([=](){ setIsConnected(true); }); queueExecute([=](){ setIsConnected(true); });
@ -87,6 +89,26 @@ Transaction* GrapheneApplication::createTransaction() const
return new Transaction; return new Transaction;
} }
void GrapheneApplication::signTransaction(Transaction* transaction) const
{
if (transaction == nullptr) return;
auto getActiveAuth = [this](graphene::chain::account_id_type id) {
return &model()->getAccount(id.instance.value)->accountObject().active;
};
auto getOwnerAuth = [this](graphene::chain::account_id_type id) {
return &model()->getAccount(id.instance.value)->accountObject().owner;
};
auto& trx = transaction->internalTransaction();
trx.set_reference_block(model()->dynamic_global_properties().head_block_id);
flat_set<public_key_type> pubKeys = wallet()->getAvailablePrivateKeys();
auto requiredKeys = trx.get_required_signatures(pubKeys, getActiveAuth, getOwnerAuth);
trx.signatures = wallet()->signDigest(trx.digest(), requiredKeys);
idump((trx));
}
Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const
{ {
func(); func();

View file

@ -68,6 +68,7 @@ public:
/// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction. /// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction.
Q_INVOKABLE Transaction* createTransaction() const; Q_INVOKABLE Transaction* createTransaction() const;
Q_INVOKABLE void signTransaction(Transaction* transaction) const;
Q_SIGNALS: Q_SIGNALS:
void exceptionThrown(QString message); void exceptionThrown(QString message);

View file

@ -352,16 +352,6 @@ QList<QPair<QString,QString>> Wallet::getAllPublicKeys(bool only_if_private)cons
return result; return result;
} }
void Wallet::sign(Transaction* transaction) const
{
if (transaction == nullptr) return;
auto& trx = transaction->internalTransaction();
flat_set<public_key_type> pubKeys = getAvailablePrivateKeys();
trx.signatures = signDigest(trx.digest(), set<public_key_type>(pubKeys.begin(), pubKeys.end()));
idump((trx));
}
QString Wallet::getPublicKey(QString label) QString Wallet::getPublicKey(QString label)
{ {
if( !isOpen() ) return QString::null; if( !isOpen() ) return QString::null;

View file

@ -127,7 +127,6 @@ class Wallet : public QObject
/** /**
* @pre !isLocked() * @pre !isLocked()
*/ */
Q_INVOKABLE void sign(Transaction* transaction) const;
vector<signature_type> signDigest(const digest_type& d, vector<signature_type> signDigest(const digest_type& d,
const set<public_key_type>& keys)const; const set<public_key_type>& keys)const;

View file

@ -65,6 +65,7 @@ FormBase {
UnlockingFinishButtons { UnlockingFinishButtons {
app: base.app app: base.app
Layout.fillWidth: true Layout.fillWidth: true
rightButtonText: qsTr("Commit")
onLeftButtonClicked: { onLeftButtonClicked: {
canceled({}) canceled({})
trx = null trx = null
@ -73,7 +74,8 @@ FormBase {
if (app.wallet.isLocked) if (app.wallet.isLocked)
app.wallet.unlock(passwordField.text) app.wallet.unlock(passwordField.text)
else { else {
app.wallet.sign(trx) app.signTransaction(trx)
app.model.broadcast(trx)
completed(trx) completed(trx)
} }
} }