Ultrain DApp開發指南

買賣虛擬貨幣
一、關於文件本指南的目的是教會你如何基於Ultrain開發DApp。我們將會涉及到你需要知道的所有內容,從工具、api到合約編寫,以及怎麼將合約與前端工程相結合。本指南中涉及到的所有專案,均已開源且被儲存和記錄在Ben的公共dapp-tutorial庫(https://github.com/benyasin/dapp-tutorial)。大家可以隨意地Fork、Clone、和改善這些指南。適用人群本指南針對未使用過Ultrain平臺進行DApp開發的入門級使用者。學習前提
學習本教程需要開發者瞭解NodeJS、TypeScript,熟悉VUE、React等前端相關知識。你將學會Ultrain核心技術架構簡介Longclaw本地開發環境搭建Robin framework命令列操作使用U3.js與鏈進行常用互動操作
使用TypeScript進行合約編寫基於前端框架編寫完整DApp專案二、技術簡介ULTRAIN透過整合閒散的計算資源,提供“信任計算 ”服務,該服務類似於雲端計算服務,是一種基於信任計算技術的獨立計算模式,為信任計算的商業模式提供計算支撐。高TPS的價值計算1)完全的去中心化設計
2)支援百萬節點,異構系統,低能耗系統接入3)TPS理論設計2萬筆/秒高效的智慧合約執行1)無程式碼行數限制2)無執行時間限制3)併發的智慧合約執行
人人可程式設計的智慧合約基於TypeScript的開發框架1)內建安全編碼規範和語法自動化檢查2)支援基於NodeJS的單元測試3)基於命令列的合約腳手架和一鍵部署4)開發者友好的合約模板
5)安全高效能的指令集可接受的使用成本,是競爭對手的1/201)EOS 350萬/年2)以太坊 250萬/年完備的隱私保護方案可程式設計零知識證明
1)可程式設計:按使用者的業務邏輯自定義,可靈活配置的零知識證明模組;可無需Setup過程(任意資產/任意邏輯)2)高效:比傳統的零知識證明運算速度提升25% - 50%3)客戶端低運算量:客戶端運算量降低為傳統方法的25%以下,可以整合到手機晶片中,極大地拓展其應用範圍R-PoS共識機制

R-PoS是一種有多項最佳化創新的混合共識演算法。Ultrain透過吸收改良VRF隨機演算法,做出大規模節點的委員會成員選舉,並借鑑了PoS的Stake機制來增強整個共識演算法的安全性和穩定性,同時結合了BFT具備快速最終確定特性的共識演算法,針對BFT進行了大量工程上的最佳化,最終研發出R-PoS共識機制。

主側鏈隨機排程技術

企業客戶透過使用信任計算服務,可以大幅降低其商業環境中的信任成本,重構其原有的商業模式,實現收入的高速增長。從全球範圍來看,Ultrain是唯一一個可以為企業客戶提供一站式信任計算商業服務的專案,也是唯一一個同時解決了高TPS、高成本和資料安全問題的專案。


Ultrain透過領先的技術研發與生態拓展能力打造可持續的、良性健康的商業模式,形成閉環。

三、環境搭建

Ultrain平臺環境可以分為線下開發環境、線上測試網環境與線上主網環境。

其中,線上主網環境需要購買資源套餐後方可使用,線上測試網環境可經水龍頭程式自行充值後使用,線下開發環境是指在本地自行構建的網路共識環境。以下篇幅重點介紹線下開發環境的搭建流程。

· 系統推薦

我們推薦首選的作業系統為MacOS,你將獲得最佳的開發體驗,其次是Linux與Windows。如果你在Windows下開發遇到一些相容性問題,請參考Windows下Dapp開發攻略 (https://developer.ultrain.io/tutorial/windows_develop)。

· Longclaw

Ultrain線下開發環境藉助於Docker來構建,所以需要你在本機上提前安裝並啟動Docker。關於Docker安裝及使用可以參考阮一峰的Docker入門教程 (http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)。

我們使用整合工具Longclaw來構建本地共識網路環境。首先在開發者網站上下載相應系統的Longclaw

(https://developer.ultrain.io/tools),安裝並啟動它。

注意:Longclaw第一次初始化環境可能要花費幾分鐘,需要你耐心等待,當出現以下介面,則說明Longclaw已經成功幫你構建了共識網路環境,也就是本地開發環境。


· 測試賬號

Longclaw為開發者預設建立了八個測試賬號,這些賬號擁有無限的資源使用權。當然如果你透過程式介面自行建立了賬號,則預設是沒有可用資源的,需要呼叫購買資源套餐的介面購買資源。相關文件參考與鏈互動一章中U3.js的對應介面用法說明。

注意:如果在Linux與Window下,Longclaw因不相容問題,導致不能正常啟動,建議直接連線線上測試網環境進行開發,相關配置可參考教程測試公鏈開發配置指南  (https://developer.ultrain.io/tutorial/testnet_guide)

四、工具使用

當有了可用的開發環境後,接下來就可以使用Robin framework建立一個DApp了。Robin framework是一個NodeJS編寫的全域性命令列程式介面。

它提供以下服務:

· 一鍵式合約初始化、編譯與部署;
· 自動化合約測試與開發;
· 友好的程式碼審查與錯誤提示;
· 大量的合約模板與示例參考;
· 指令碼式與可配置化部署流程;
· 互動式合約日誌控制檯輸出;

要求

NodeJS 8.0+
Linux、MacOSX、Windows

安裝

sudo npm install -g robin-framework

建立工程

執行robinorrobin -h來檢視所有的robin子命令

要啟動專案,首先需要建立一個新的空目錄,然後進入目錄:

mkdir testing cd testing

然後初始化一個專案。使用-cor--contract來指定名稱。此時,你有多個模板可以選擇,預設的是純合約專案,其餘的是帶介面的DApp框架。

robin init


合約專案目錄結構:

|--build        // built WebAssembly targets
|--contract     // contract source files
|--migrations   // assign built files location and account who will deploy the contract
|--templates    // some contract templates that will guide you
|--test         // test files
|--config.js    // configuration
...

語法檢查

在robin-lint的幫助下,藉助定製的tslint專案,您將找到錯誤和警告,然後快速修復它們。 只需進入專案的根目錄並執行:robin lint

編譯合約

依賴於ultrascript,合約原始檔將會被編譯為WebAsssembly目標檔案: *.abi, *.wast, *.wasm. 只需進入專案的根目錄並執行:robin build

部署合約

更新配置檔案config.js和migrate.js, 確保你已正確連線上一個Ultrain節點。如果你正在使用longclaw初始化的本地環境,那麼使用預設配置即可。也可以是你定製的節點。 只需進入專案的根目錄並執行:robin deploy

測試合約

參考測試目錄下*.spec.js檔案, 編寫測試用例來覆蓋你的合約中的所有用例場景。Robin提供給你一些測試工具類,比如mocha,chai,u3.jsandu3-utils, 尤其是用在處理非同步測試,只需進入專案的根目錄並執行:robin test

整合UI

如果你想將一個合約專案升級為帶介面的DAPP專案, 使用UI子命令。你有多個框架可以選擇,它們分別是vue-boilerplate、react-boilerplate和react-native-boilerplate。只需進入專案的根目錄並執行:robin ui

五、與鏈互動

U3.js是用JavaScript封裝的負責與鏈互動的通用庫。而Robin framework引用了U3.js,藉助它的介面實現了合約的deploy上鍊。

應用環境

瀏覽器(ES6)或 NodeJS

如果你想整合u3.js到react native環境中,有一個可行的方法,藉助rn-nodeify實現,參考示例U3RNDemo(https://github.com/benyasin/U3RNDemo)

使用方法

一、如果是在瀏覽器中使用u3,請參考以下用法:






test







二、 如果是在NodeJS環境中使用u3,請參照以下用法:

· 安裝u3

npm install u3.js或yarn add u3.js

· 初始化

const { createU3 } = require('u3.js/src');
let config = {
httpEndpoint: 'http://127.0.0.1:8888',
httpEndpoint_history: 'http://127.0.0.1:3000',
chainId: '0eaaff4003d4e08a541332c62827c0ac5d96766c712316afe7ade6f99b8d70fe',
keyProvider: ['PrivateKeys...'],
broadcast: true,
sign: true
}
let u3 = createU3(config);

u3.getChainInfo((err, info) =>{
if (err) {throw err;}
console.log(info);
});

配置

全域性配置

· httpEndpoint string - 鏈實時API的http或https地址。如果是在瀏覽器環境中使用u3,請注意配置相同的域名。
· httpEndpoint_history string - 鏈歷史API的http或https地址。如果是在瀏覽器環境中使用u3,請注意配置相同的域名。
· chainId 鏈唯一的ID。鏈ID可以透過 [httpEndpoint]/v1/chain/get_chain_info獲得。
· keyProvider [array|string|function] - 提供私鑰用來簽名交易,提供用於簽名事務的私鑰。 如果提供了多個私鑰,不能確定使用哪個私鑰,可以使用呼叫get_required_keysAPI 獲取要使用簽名的金鑰。如果是函式,那麼每一個交易都將會使用該函式。如果這裡不提供keyProvider,那麼它可能會Options配置項提供在每一個action或每一個transaction中。

· expireInSeconds number - 事務到期前的秒數,時間基於nodultrain的時間。
· broadcast [boolean=true] - 預設是true。使用true將交易釋出到區塊鏈,使用false將獲取簽名的事務。
· verbose [boolean=false] - 預設是false。詳細日誌記錄。
· debug [boolean=false] - 預設是false。低階除錯日誌記錄。
· sign [boolean=true] - 預設是true。使用私鑰簽名交易,保留未簽名的交易避免了提供私鑰的需要。
· logger
· 預設日誌配置。

logger: {
log: config.verbose ? console.log : null,  // 如果值為null,則禁用日誌
error: config.verbose ? console.error : null,
}

Options配置項

Options可以在方法引數之後新增,Authorization應用於單獨的actions。比如:

options = {
authorization: 'admin@chaindaily',
broadcast: true,
sign: true
}

u3.transfer('alice', 'bob', '1.0000 UGAS', '', options)

· authorization [array|auth] - 指明賬號和許可權,典型地應用於多重簽名的配置中。Authorization必須是一個字串格式,形如admin@chaindaily。
· broadcast [boolean=true] - 預設是true。使用true將交易釋出到區塊鏈,使用false將獲取簽名的事務。
· sign [boolean=true] - 預設是true。使用私鑰簽名交易。保留未簽名的交易避免了提供私鑰的需要。
· keyProvider [array|string|function] - 就像global配置項中的keyProvider一樣,這裡的配置可以以覆蓋全域性配置的形式為每一個action或每一個transaction提供單獨的私鑰。

await u3.anyAction('args', {keyProvider})
await u3.transaction(tr ={
tr.anyAction()}, {keyProvider}
)

建立賬號

建立賬號需要花費creator賬號的一些代幣,為新賬號抵押部分RAM和頻寬。

const u3 = createU3(config);
const name = 'abcdefg12345';//普通賬號需要滿足規則:必須為12345abcdefghijklmnopqrstuvwxyz中的12位
let params = {
creator: 'ben',
name: name,
owner: pubkey,
active: pubkey,
updateable: 1,//可選,賬號是否可以更新(更新合約)
};
await u3.createUser(params);

轉賬

轉賬方法使用非常頻繁,UGAS的轉賬需要呼叫系統合約utrio.token。

const u3 = createU3(config);
const c = await u3.contract('utrio.token')

// 使用位置引數
await c.transfer('ben', 'bob', '1.2000 UGAS', '')
// 使用名稱引數
await c.transfer({from: 'bob', to: 'ben', quantity: '1.3000 UGAS', memo: ''})

簽名

使用{ sign: false, broadcast: false }建立一個u3例項並且做一些action, 然後將未簽名的交易傳送到錢包中。

const u3_offline = createU3({ sign: false, broadcast: false });
const c = u3_offline.contract('utrio.token');
let unsigned_transaction = await c.transfer('ultrainio', 'ben', '1 UGAS', 'uu');

在錢包中你可以提供私鑰或助記詞來簽名,並將簽名後的交易傳送到鏈上。

const u3_online = createU3();
let signature = await u3_online.sign(unsigned_transaction, privateKeyOrMnemonic, chainId);
if (signature) {
let signedTransaction = Object.assign({}, unsigned_transaction.transaction, { signatures: [signature] });
let processedTransaction = await u3_online.pushTx(signedTransaction);
}

資源

呼叫合約只會消耗合約Owner的資源,所以如果你想部署一個合約,請先購買一些資源。

· resourcelease(payer,receiver,slot,days)

const u3 = createU3(config);
const c = await u3.contract('ultrainio')

await c.resourcelease('ben', 'bob', 1, 10);// 1 slot for 10 days

透過以下方法查詢資源詳情。

const resource = await u3.queryResource('abcdefg12345');
console.log(resource)

合約

部署合約

部署合約需要提供包含目標檔案為.abi,.wast,*.wasm 的三個檔案的資料夾.

· deploy(contracts_files_path, deploy_account) 第一個引數為合約目標檔案的絕對路徑,第二個合約部署者賬號。

const u3 = createU3(config);
await u3.deploy(path.resolve(__dirname, '../contracts/token/token'), 'bob');

呼叫合約

const u3 = createU3(config);
const c = await u3.contract('ben');
await c.transfer('bob', 'ben', '1.0000 UGAS','');

//或者像這樣呼叫
await u3.contract('ben').then(sm =>
sm.transfer('bob', 'ben', '1.0000 UGAS','')
)

// 一筆交易也可以包含多個合約中的多個action
await u3.transaction(['ben', 'bob'], ({sm1, sm2}) =>{
sm1.myaction(..)
sm2.myaction(..)
})

發行代幣

const u3 = createU3(config);
const account = 'bob';
await u3.transaction(account, token =>{
token.create(account, '10000000.0000 DDD');
token.issue(account, '10000000.0000 DDD', 'issue');
});

const balance = await u3.getCurrencyBalance(account, account, 'DDD')
console.log('currency balance', balance)

事件

Ultrain提供了一個事件註冊監聽機制用來解決非同步場景下業務需求.客戶端首先訂閱一個事件,提供一個用來接收訊息的地址,當合約中的某個方法觸發時,該地址會收到來自鏈的推送訊息。

訂閱/取消訂閱

· registerEvent(deployer, listen_url)
· unregisterEvent(deployer, listen_url)

deployer: 合約的部署者賬號
listen_url: 接收訊息的地址

注意: 如果你是在本地docker環境中使用改機制,請確認接收地址是一個可以從docker訪問到的本地宿主地址.

const u3 = createU3(config);
const subscribe = await u3.registerEvent('ben', 'http://192.168.1.5:3002');

//or
const unsubscribe = await u3.unregisterEvent('ben', 'http://192.168.1.5:3002');

監聽

const { createU3, listener } = require('u3.js/src');
listener(function(data) {
// do callback logic
console.log(data);
});

U3Utils.test.wait(2000);

//must call listener function before emit event
const contract = await u3.contract(account);
contract.hi('ben', 30, 'It is a test', { authorization: [`admin@chaindaily`] });

六、合約編寫

Ultrain使用類JavaScript的語言來編寫智慧合約,這個類JavaScript的語言以TypeScript為原型,透過擴充套件的資料型別標誌符,來達到強型別語言的程式設計語法。

系統內建的方法

function NAME(str: string): u64 : 方法**NAME()**用來將一個string轉成一個account_name型別.str的字元長度不超過12個字元, 內容只能包括以下字元(不能以.結尾):.012345abcdefghijklmnopqrstuvwxyz

function RNAME(account: account_name): string : 方法**RNAME()用來將一個account_name型別轉為string型別, 它是NAME()**方法的反向方法。

function ACTION(str: string): Action : 方法**ACTION()**將一個string型別轉為Action型別,str的長度不超過21個字元, 內容只能包括以下字元(不能包含.):._0-9a-zA-Z. Action類封裝了action相關的資訊。

Action.sender : 當前transaction的發起者, account_name型別。
Action.receiver : 當前transaction的接收者, 即合約帳戶, account_name型別。
Block.number : head block的塊高。
Block.id : head block的id,sha256的hash值。
Block.timestamp : head block的時間戳,從EPOCH開始的秒數。

編寫第一個合約Hello world

import { NAME, RNAME } from "ultrain-ts-lib/src/account";
import { Log } from "ultrain-ts-lib/src/log";
import { Contract } from "ultrain-ts-lib/lib/contract";

class HelloWorld extends Contract {

@action
hi(name: account_name, age: u32, msg: string): void {
Log.s("hi: name = ").s(RNAME(name)).s(" age = ").i(age, 10).s(" msg = ").s(msg).flush();
}
}

我們以上程式碼做如下說明:

1. import: 用來引入其它檔案中定義的類和方法,詳細用法可參考 typescript(https://www.tutorialspoint.com/typescript/index.htm)的說明。
2. extends Contract: 合約都需要派生自Contract,而且一個專案中只能有一個Contract。
3. @action: 申明一個合約方法。只有@action標誌的方法,才能被呼叫。
4. Log: 列印Log。需要在config.ini檔案中配置 contracts-console = true 才能列印到終端。

在Action中Return資訊

為了便於在呼叫方與節點中傳遞部分執行狀態資訊,引入Return模組,Return模組返回的資料會附加在http的response中,呼叫方可以透過分析response得到Return的資訊。

需要強調的是,Return的資訊僅僅是在一個節點(host_url )上預執行的結果,並非區塊鏈網路共識的結果。也就是說, Return返回的結果, 並不是最終交易執行的結果。

Return的資訊只供參考,它可能與區塊鏈網路共識結果不一致。

要Return資訊,可以在action呼叫中,透過Return,ReturnArray方法來完成。Return資訊有以下需要注意的點:

NOTICE

1. Return的message是有長度限制的,預設的message長度為128個character。(int型資料會轉成對應的string)。如果是在側鏈中使用,可以在config.ini檔案中配置 contract-return-string-length 來擴充套件長度限制。
2. 只支援Return基本資料型別int和string, 以及int[]和string[]。
3. 可以呼叫Return或ReturnArray多次,資訊將被concat。
4. 超出長度限制的資訊,會直接丟棄,不會丟擲異常。

Return資訊的示例

class HelloContract extends Contract{
@action
on_hi(name: u64, age: u32, msg: string): void {
Return("call hi() succeed.");
ReturnArray([1,2,3]);
}
}

執行正常的情況下,Return的結果是call hi() succeed.123

資產查詢和轉移

在合約中,可以查詢一個帳號在ultrainio.token合約中的資產,即ultrain平臺資產。查詢資產使用Asset.balanceOf(who: account_name): Asset方法。 轉移ultrain平臺資產,可以使用Asset.transfer(from: account_name, to: account_name, val: Asset, memo: string): void方法。

使用詳情請參考示例balance (https://github.com/ultrain-os/ultrain-ts-lib/blob/master/example/balance/balance.ts)。

import "allocator/arena";
import { Contract } from "ultrain-ts-lib/src/contract";
import { Asset } from "ultrain-ts-lib/src/asset";
import { ultrain_assert } from "ultrain-ts-lib/src/utils";
class BalanceContract extends Contract {

@action
transfer(from: account_name, to: account_name, bet: Asset): void {

let balance = Asset.balanceOf(from);
ultrain_assert(balance.gte(bet), "your balance is not enough.");

balance.prints("banalce from: ");

Asset.transfer(from, to, bet, "this is a transfer test");
}
}

NOTICE 使用Asset.transfer命令轉移資產時,需要保證from的許可權已經授權給了utrio.code,在使用命令列的情況下,可以透過以下命令來授權:

clutrain set account permission $from active '{"threshold": 1, "keys":[{"key":"$PubKey_of_from", "wieght": 1}], "accounts": [{"permission": {"actor": "$from", "permission": "utrio.code"}, "weight": 1]}' owner -p $from

$from是需要授權的帳號。

持久化儲存

Ultrain的智慧合約提供了DBManager來儲存合約資料到資料庫中。不同於以太坊會自動儲存資料,Ultrain需要明確的呼叫API來儲存、讀取資料。

Serializable介面

Serializable是一個Interface, 定義以下三個方法:

import {DataStream} from "ultrain-ts-lib/src/datastream";
export interface Serializable {
deserialize(ds: DataStream): void;
serialize(ds : DataStream) : void;
primaryKey(): u64;
}

deserialize(ds: DataStream): void;

· 方法用來做反序列化工作,從DataStream的位元組流中讀取資料進行初始化工作。
· serialize(ds: DataStream): void; 方法用來做序列化工作,將class的資料寫入到位元組流中。
· primaryKey(): u64; 標誌一個primary key。如果這個class將作為一條獨立的記錄寫入資料庫,那primaryKey()返回的資料將成為資料庫中的primary key.

NOTICE

1. 一個實現了ISerialzable介面的class,編譯器將自動實現以上三個方法,並將class中的成員變數都序列化/反序列化。如果需要單獨override某一個/全部方法,則可以手動實現對應的方法。
2. 如果要排除某個成員變數,以避免序列化和反序列化,可以使用 @ignore 註解。
3. 如果要指定某個成員變數為primaryKey,可以使用 @primaryid 註解。需要注意的是,被註解為@primaryid的變數必須是u64型別,如果沒有變數被註解為@primaryid,則primaryKey()方法預設使用0 作為返回值。
4. 如果使用了@註解,同時又override了serialize()、deserialize()、primaryKey()方法中的某一個(或全部),編譯器將優先使用override的方法。

對於Serializable介面的使用,舉例如下:

class Person implements Serializable{
name: string;
age: u32;
sex: string;
salary: u32;
@ignore
address: string; // 被忽略,不序列化和反序列化

constructor() {
this.name = "xx";
//...
}
// 重寫primaryKey()方法,返回Person的id
primaryKey(): u64 {
return NAME(this.name);
}
}

可序列化儲存的資料

儲存到資料庫中的資料,必須是能夠序列化和反序列化。可以序列化儲存的資料有以下幾類:

1. 內建基本資料型別: u8/i8, u16/i16, u32/i32, u64/i64, boolean, string。 有一些型別其實也是基本資料型別的別名,如account_name。
2. 基本資料型別的一維陣列: u8[], i8[], ..., string[]
3. 實現了Serializable介面的類, 如上的Person。
4. 實現了Serializable介面的類的一維陣列,如Person[]。

宣告合約中DB的table資訊

如果合約中需要使用到DB進行資料存取,則需要在具體的Contract類中註解說明table的資訊。 如下簡單的一份虛擬碼:

class Person implements Serializable {
name: string;
sex: string;
}

class Car implements Serializable {
model: string;
power: u32;
color: string;
}

@database(Person, "persons")
@database(Car, "cars")
// @database() if any more
clas MyContract extends Contract {
//...
// your logic here
}

上述程式碼將會生成兩張表格: "persons"和"cars"。 需要注意的是,@database註解中的Person和Car兩個類,必須實現Serializable介面。

資料庫讀寫

Contract中資料存取要透過DBManager來管理。

DBManager的定義:

export class DBManager{
constructor(tblname: u64, owner: u64, scope: u64) {}
public cursor(): Cursor{}
public emplace(payer: u64, obj: T): void {}
public modify(payer: u64, newobj: T): void {}
public exists(primary: u64): boolean {}
public get(primary: u64, out: T): boolean { }
public erase(obj: T): void {}
}

constructor()方法接收三個引數,

· tblname: u64表示表名; owner:u64表示這個表在哪個合約中,一般的,owner和該合約的receiver是一樣的。 scope: u64表示表中的一個上下文。
· cursor()方法讀取資料表中的所有記錄。
· emplace()方法向表中加入一條記錄。 payer表示這個帳號將為資料儲存付費, obj是一個Serializable的物件,將資料存入DB。
· modify()方法更新表中的資料。 payer表示這條記錄的建立者、付費方; newobj是更新後的資料,newobj的primaryKey對應的物件會被更新。
· exists()方法判斷一個primaryKey是否存在。
· get()方法從DB中讀取primary對應的記錄,並反序列化到out中。
· erase()方法用來刪除一條記錄,obj的primaryKey對應的記錄如果存在,將被刪掉。

NOTICE table沒有方法可以顯式刪除,只有當table中的記錄都刪掉時,table會自動被刪除。

使用Cursor遍歷所有記錄

我們提供了cursor來遍歷所有的記錄,但是必須明白,這個操作非常非常低效,因為在當呼叫cursor()方法時,會將所有的表中的資料都載入到記憶體裡面。如果表中的資料很多的話,那這個交易將會被cursor方法阻塞,從而導致交易超時失敗。 如下示例演示了怎樣使用cursor:

let cursor = this.db.cursor();
Log.s("cursor.count =").i(cursor.count).flush();

while(cursor.hasNext()) {
let p: Person = cursor.get();
p.prints();
cursor.next();
}

table裡面scope和primary key的關係

table中的資料,可以按scope來分類,也可以透過primary key來分類。儘管它們都可以達到分類資料的效果,但是在table中,scope和primary key是兩個不同的維度,它們之間的關係,大概可用下面的結構來表示:

|--table
|----scope1
|--------primaryKey_1
|--------primaryKey_2
|--------........
|----scope2
|--------primaryKey_x
|--------primaryKey_y
|--------.......

在不同的scope下面,primary key可以取相同的值。

使用示例

DB的讀寫操作,請參考示例Person (https://github.com/ultrain-os/ultrain-ts-lib/blob/master/example/person/person.ts)。

import "allocator/arena";
import { Contract } from "ultrain-ts-lib/src/contract";
import { Log } from "ultrain-ts-lib/src/log";
import { ultrain_assert } from "ultrain-ts-lib/src/utils";
import { DBManager } from "ultrain-ts-lib/src/dbmanager";
import { NAME } from "ultrain-ts-lib/src/account";

class Person implements Serializable {
// name: string;
name: string
age: u32;
salary: u32;

primaryKey(): u64 { return NAME(this.name); }

prints(): void {
Log.s("name = ").s(this.name).s(", age = ").i(this.age).s(", salary = ").i(this.salary).flush();
}
}

const tblname = "humans";
const scope = "dept.sales";

@database(Person, "humans")
// @database(SomeMoreRecordStruct, "other_table")
class PersonContract extends Contract {

db: DBManager;

public onInit(): void {
this.db = new DBManager(NAME(tblname), this.receiver, NAME(scope));
}


public onStop(): void {

}

constructor(code: u64) {
super(code);
this._receiver = code;

this.onInit();
}

@action
add(name: string, age: u32, salary: u32): void {
let

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;