EOS開發教程(2.6) 添加行内action

in #eoslast year

簡介
在前面的教程中,我們已經簡單的展示了授權addressbook 成為多索引表的過程。在本教程中,我將學到如何構建action,如何從壹個合約發送這些action。
Step 1:把eosio.code添加到許可中
要想能從addressbook中發送行內action,需要把eosio.code許可添加到合約帳號的活躍許可(active permission)中去。打開妳的終端,運行下面的代碼:
cleos set account permission addressbook active --add-code
eosio.code授权是假授权,以此提高安全性,以及让合约能执行行内action。
Step 2:通知Action
打開上壹個教程中被授權的addressbook.cpp合約。寫壹個action,它能在壹筆交易發生時發出“交易憑證”。為了實現這點,在addressbook 類裏創建壹個輔助函數。
[[eosio::action]] void notify(name user, std::string msg) {}
這個函數非常簡單,接受壹個name類型的用戶帳號,壹個string類型的消息。用戶參數表明是哪個用戶接收了發出的消息。
Step3:使用require_recipient把action拷貝到發送者
需要把交易拷貝給用戶,這樣它才能被當作收據。為了實現這點,要使用require_recipient 方法。調用 require_recipient 添加壹個帳號到require_recipient,確保這些帳號接收到被執行的action發出的通知。這個通知類似於發送壹個該action的“carbon副本”到require_recipient中的帳號。
[[eosio::action]] void notify(name user, std::string msg) { require_recipient(user); }
這個action十分簡單,任何用戶都能調用這個函數,然後偽造壹個這個合約的收據。這可能會被用於惡意的行為,這是壹個缺陷。為了糾正這點,就要確保用於調用這個action的驗證是出自它本身的,在本例中,使用get_self
[[eosio::action]] void notify(name user, std::string msg) { require_auth(get_self()); require_recipient(user); }
如果用戶bob直接調用了這個函數,但是傳遞的參數卻是alice,那麽這個函數就會拋出壹個異常。
Step4:通知輔助(helper)方法發送行內交易
由於行內action會被多次調用所以我們可以寫壹個輔助函數,來最大化代碼重用。在合約的私有域內,定義壹個方法:
... private: void send_summary(name user, std::string message){}
在這個輔助方法內構建壹個action,然後把它發送出去。
Step 5: action構造器
修改addressbook 合約,在用戶每次執行該合約的action的時候,發送壹個收據給該用戶。
首先,要處理“創建記錄”這個問題。當在表中找不到記錄的時候會出現這個問題,也就是當iterator == addresses.end()為true的時候。
把這個對象保存到壹個叫做notification的action變量中去。
... private: void send_summary(name user, std::string message){ action( //permission_level, //code, //action, //data ); }
壹個action構造器需要幾個參數。
· 壹個permission_level 結構
· 調用的合約(使用eosio::name類型進行初始化)
· action(使用eosio::name類型初始化)
· 傳遞到action中的數據,與被調用的action相關的位置元組。
授權結構
在這個合約中,授權應該由該合約的活躍(active)授權使用進行get_self()授權。提醒壹下,要在行內使用“活躍授權”,妳需要把妳的合約的活躍授權發送給eosio.code的pseudo-authority
... private: void send_summary(name user, std::string message){ action( permission_level{get_self(),"active"_n}, ); }
“代碼(code)”,也就是“部署合約的帳號”
由於是在這個合約中使用get_self調用的action。”addressbook”_n 在這裏也能運行,但是如果這個合約是以別的帳號的名義調用的,那麽它就不能運行。正基於此,get_self() 的優先級更高。
... private: void send_summary(name user, std::string message){ action( permission_level{get_self(),"active"_n}, get_self(), //action //data ); }
action
我們在前面把notify action定義為從行內action被調用的。這裏我們使用_n 操作符。
... private: void send_summary(name user, std::string message){ action( permission_level{get_self(),"active"_n}, get_self(), "notify"_n, //data ); }
數據
最後,定義傳遞到這個action中的數據。通知函數接收兩個參數,壹個name,壹個string。Action構造器的數據的類型為bytes,所以我們使用make_tuple,通過std C++庫可以調用。在元組中傳遞的數據是關於位置的,由被調用的action所接收的參數的順序決定,
· 把user變量當作參數傳給upsert()action。
· 把用戶的名字,傳遞給通知(notify)action的消息拼接為壹個字符串。
... private: void send_summary(name user, std::string message){ action( permission_level{get_self(),"active"_n}, get_self(), "notify"_n, std::make_tuple(user, name{user}.to_string() + message) ); }
發送action
最後,使用action結構的send方法發送action
... private: void send_summary(name user, std::string message){ action( permission_level{get_self(),"active"_n}, get_self(), "notify"_n, std::make_tuple(user, name{user}.to_string() + message) ).send(); }
Step 6: 調用輔助函數並註入相關消息
現在我們定義了輔助函數,它會從相關的位置被調用。有三個地方可以調用notify的輔助函數:
· 在合約emplaces 後的壹條新記錄:send_summary(user, "successfully emplaced record to addressbook");
· 在合約modifies 後的壹條現有記錄:send_summary(user, "successfully modified record in addressbook.");
· 在合約erases 後的壹條已有記錄:send_summary(user, "successfully erased record from addressbook");
Step 7: 重新編譯,並重新生成ABI文件
壹切就緒,下面是當前addressbook 合約的完整內容:
#include <eosio/eosio.hpp> #include <eosio/print.hpp> using namespace eosio; class [[eosio::contract("addressbook")]] addressbook : public eosio::contract { public: addressbook(name receiver, name code, datastream<const char*> ds): contract(receiver, code, ds) {} [[eosio::action]] void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) { require_auth(user); address_index addresses(get_first_receiver(), get_first_receiver().value); auto iterator = addresses.find(user.value); if( iterator == addresses.end() ) { addresses.emplace(user, [&]( auto& row ) { row.key = user; row.first_name = first_name; row.last_name = last_name; row.age = age; row.street = street; row.city = city; row.state = state; }); send_summary(user, " successfully emplaced record to addressbook"); } else { addresses.modify(iterator, user, [&]( auto& row ) { row.key = user; row.first_name = first_name; row.last_name = last_name; row.age = age; row.street = street; row.city = city; row.state = state; }); send_summary(user, " successfully modified record to addressbook"); } } [[eosio::action]] void erase(name user) { require_auth(user); address_index addresses(get_first_receiver(), get_first_receiver().value); auto iterator = addresses.find(user.value); check(iterator != addresses.end(), "Record does not exist"); addresses.erase(iterator); send_summary(user, " successfully erased record from addressbook"); } [[eosio::action]] void notify(name user, std::string msg) { require_auth(get_self()); require_recipient(user); } private: struct [[eosio::table]] person { name key; std::string first_name; std::string last_name; uint64_t age; std::string street; std::string city; std::string state; uint64_t primary_key() const { return key.value; } uint64_t get_secondary_1() const { return age;} }; void send_summary(name user, std::string message) { action( permission_level{get_self(),"active"_n}, get_self(), "notify"_n, std::make_tuple(user, name{user}.to_string() + message) ).send(); }; typedef eosio::multi_index<"people"_n, person, indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>> > address_index; };
打开你的终端,进入 CONTRACTS_DIR/addressbook
cd CONTRACTS_DIR/addressbook
重新編譯合約,由於上面做的更改會影響到ABI文件,所以要使用 — abigen標識。如果上面的步驟都沒問題的話,這壹步也不會報錯。
eosio-cpp -o addressbook.wasm addressbook.cpp --abigen
EOSIO上的只能合約是可以升級的,所以這個合約可以被重新部署。
cleos set contract addressbook CONTRACTS_DIR/addressbook
Publishing contract... executed transaction: 1898d22d994c97824228b24a1741ca3bd5c7bc2eba9fea8e83446d78bfb264fd 7320 bytes 747 us # eosio <= eosio::setcode {"account":"addressbook","vmtype":0,"vmversion":0,"code":"0061736d0100000001a6011a60027f7e0060077f7e... # eosio <= eosio::setabi {"account":"addressbook","abi":"0e656f73696f3a3a6162692f312e30010c6163636f756e745f6e616d65046e616d65...
成功!
Step 8: 測試
現在合約已經被部署了,那麽就來測試壹下吧。在之前的教程中,alice的addressbook記錄在測試的時候被刪掉了,所以調用upsert將會觸發剛剛寫到“create”裏的行內action。
在終端運行以下命令
cleos push action addressbook upsert '["alice", "alice", "liddell", 21, "123 drink me way", "wonderland", "amsterdam"]' -p [email protected]
cleos會返回壹些數據,包括在交易中執行的所有action。
executed transaction: e9e30524186bb6501cf490ceb744fe50654eb393ce0dd733f3bb6c68ff4b5622 160 bytes 9810 us # addressbook <= addressbook::upsert {"user":"alice","first_name":"alice","last_name":"liddell","age":21,"street":"123 drink me way","cit... # addressbook <= addressbook::notify {"user":"alice","msg":"alicesuccessfully emplaced record to addressbook"} # alice <= addressbook::notify {"user":"alice","msg":"alicesuccessfully emplaced record to addressbook"}
上個日誌中的最後壹條是發送給alice的addressbook::notify action。使用cleos get actions 展示與alice相關的以及發送到alice的action。
cleos get actions alice

seq when contract::action => receiver trx id... args ================================================================================================================ # 62 2018-09-15T12:57:09.000 addressbook::notify => alice 685ecc09... {"user":"alice","msg":"alice successfully added record to ad...