構(gòu)建一筆交易
通過MakeTransaction(TransferOutput[] outputs, UInt160 from = null)
函數(shù)構(gòu)建。
UInt160[] accounts;
if (from is null)
{
accounts = GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray();
}
else
{
if (!Contains(from))
throw new ArgumentException($"The address {from.ToString()} was not found in the wallet");
accounts = new[] { from };
}
如果未指定from賬戶肆氓,則首先調(diào)用GetAccount()
獲取錢包中所有非Lock和非WatchOnly的賬戶列表袍祖,按照腳本哈希排序。
若指定from賬戶谢揪,則判斷賬戶是否包含在錢包中蕉陋,不包含則拋出異常。
將獲取的賬戶加入accounts拨扶。
把outputs中的輸出按照(assetId, group, sum)分組凳鬓,其中assetId表示需要輸出的資產(chǎn)ID,group表示輸出該類型資產(chǎn)的output集合,sum表示輸出資產(chǎn)的總量患民。
對于每一種資產(chǎn)缩举,分別對每個賬戶構(gòu)建腳本,進入虛擬機執(zhí)行得到資產(chǎn)余額匹颤,并記錄在balances中仅孩。最后將所有余額相加得到資產(chǎn)總余額,如果小于需要輸出的總量印蓖,則拋出余額不足異常辽慕。余額充足則進入下一步。
foreach (TransferOutput output in group)
{
balances = balances.OrderBy(p => p.Value).ToList();
var balances_used = FindPayingAccounts(balances, output.Value.Value);
cosigners.UnionWith(balances_used.Select(p => p.Account));
foreach (var (account, value) in balances_used)
{
sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value);
sb.Emit(OpCode.THROWIFNOT);
}
}
最主要的便是FindPayingAccounts(balances, output.Value.Value);根據(jù)balances和輸出的數(shù)量選擇合適的賬戶付款另伍。
如果balances剛好與輸出相等鼻百,則所有賬戶即為付款賬戶。
否則摆尝,遍歷每個賬戶温艇,如果某個賬戶的余額等于輸出,則該賬戶為付款賬戶堕汞。如果賬戶余額大于輸出勺爱,則該賬戶為付款賬戶,數(shù)量為需要輸出數(shù)量讯检,并更新賬戶余額琐鲁。
如果所有賬戶的余額均小于輸出,則從余額最多的賬戶開始向下遍歷人灼,依次加入付款賬戶围段,直到某賬戶余額大于需要付款的值,此時從余額最小的賬戶向上遍歷投放,找到第一個余額大于需要輸出的賬戶奈泪,將該賬戶作為付款賬戶,并更新余額司恳。
例如有7個賬戶余額分別為1刽锤,2,3贴汪,4冯遂,5蕊肥,6,7蛤肌,輸出為15.5壁却。首先會選擇7,6寻定,兩個賬戶儒洛,此時5>2.5,則從小到大遍歷狼速,其中第一個大于2.5的賬戶為3,所以輸出3卦停。最終的付款賬戶為(7向胡,6,3)惊完,值為(7僵芹,6,2.5)小槐。
隨后記錄所有付款賬戶地址拇派,并按照賬戶依次構(gòu)造虛擬機APPCALL轉(zhuǎn)賬腳本。計算所有賬戶Gas余額凿跳,并調(diào)用
MakeTransaction(snapshot, attributes, script, balances_gas);
該函數(shù)首先將腳本進入虛擬機test模式件豌,計算Gas消耗量。如果Gas消耗超過免費額度控嗜,則超過部分向上取整作為SystemFee茧彤。(對remainder<0不清楚)
接下來計算NetworkFee,
foreach (UInt160 hash in hashes)
{
byte[] witness_script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script;
if (witness_script is null) continue;
if (witness_script.IsSignatureContract())
{
size += 66 + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null);
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
int size_inv = 65 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n;
}
else
{
//We can support more contract types in the future.
}
}
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
NetworkFee分單簽和多簽的情況。且NetworkFee主要由交易size費用以及操作碼費用兩部分組成疆栏。
對于單簽曾掂,首先是區(qū)塊的固定部分size:
int size = Transaction.HeaderSize + attributes.GetVarSize()
+ script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);
加上腳本的size:
size += 66 + witness_script.GetVarSize();
66代表單簽?zāi)_本驗證時需要添加的PUSHBYTES64+64Bytes的長度(還有1不知道是什么)。
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null);
這部分包括單簽驗證需要的opcode的費用壁顶。
最后加上所有size與每字節(jié)的費用:
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
就得到了NetworkFee的總量珠洗。
類似的,對于多簽?zāi)_本:
int size_inv = 65 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n;
同樣是包括總的size費用以及多簽驗證腳本的操作碼對應(yīng)總費用若专。
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
最后計算SystemFee和NetworkFee的和许蓖,如果Gas余額大于之和,則返回Tx,否則拋出異常蛔糯。