1.智能合約的部署
- 首先在gui里加載已經(jīng)編寫好的合約avm睡蟋,然后填入相關(guān)信息以及參數(shù)列表和返回值踏幻。這里我們的合約輸入是兩個(gè)string,輸出為一個(gè)string,所以參數(shù)列表填入0707,返回值07戳杀。
- 調(diào)用GetTransaction()该面,其作用為從gui里讀出合約相關(guān)信息,然后根據(jù)信息創(chuàng)建一個(gè)合約腳本信卡。
public InvocationTransaction GetTransaction()
{
byte[] script = textBox8.Text.HexToBytes();
byte[] parameter_list = textBox6.Text.HexToBytes();
ContractParameterType return_type = textBox7.Text.HexToBytes().Select(p => (ContractParameterType?)p).FirstOrDefault() ?? ContractParameterType.Void;
ContractPropertyState properties = ContractPropertyState.NoProperty;
if (checkBox1.Checked) properties |= ContractPropertyState.HasStorage;
if (checkBox2.Checked) properties |= ContractPropertyState.HasDynamicInvoke;
string name = textBox1.Text;
string version = textBox2.Text;
string author = textBox3.Text;
string email = textBox4.Text;
string description = textBox5.Text;
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitSysCall("Neo.Contract.Create", script, parameter_list, return_type, properties, name, version, author, email, description);
return new InvocationTransaction
{
Script = sb.ToArray()
};
}
EmitSysCall后面會(huì)在加載虛擬機(jī)時(shí)執(zhí)行下面的方法構(gòu)造一個(gè)智能合約隔缀。
private bool Contract_Create(ExecutionEngine engine)
{
TR.Enter();
byte[] script = engine.EvaluationStack.Pop().GetByteArray();
if (script.Length > 1024 * 1024) return TR.Exit(false);
ContractParameterType[] parameter_list = engine.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray();
if (parameter_list.Length > 252) return TR.Exit(false);
ContractParameterType return_type = (ContractParameterType)(byte)engine.EvaluationStack.Pop().GetBigInteger();
ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.EvaluationStack.Pop().GetBigInteger();
if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
string name = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
string version = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
string author = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
string email = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
if (engine.EvaluationStack.Peek().GetByteArray().Length > 65536) return TR.Exit(false);
string description = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
UInt160 hash = script.ToScriptHash();
ContractState contract = contracts.TryGet(hash);
if (contract == null)
{
contract = new ContractState
{
Script = script,
ParameterList = parameter_list,
ReturnType = return_type,
ContractProperties = contract_properties,
Name = name,
CodeVersion = version,
Author = author,
Email = email,
Description = description
};
contracts.Add(hash, contract);
contracts_created.Add(hash, new UInt160(engine.CurrentContext.ScriptHash));
}
engine.EvaluationStack.Push(StackItem.FromInterface(contract));
return TR.Exit(true);
}
最后返回了一個(gè)InvocationTransaction,其Script包含合約的信息傍菇。
textBox9.Text = textBox8.Text.HexToBytes().ToScriptHash().ToString();
即Script Hash的值為智能合約代碼的hash值猾瘸,后面合約調(diào)用也是根據(jù)這個(gè)hash區(qū)尋找指定的腳本。這里可能會(huì)造成一個(gè)問題丢习,如果你和別人的智能合約代碼完全相同牵触,則這兩個(gè)腳本會(huì)指向同一個(gè)地址,可能會(huì)出現(xiàn)異常咐低。
-
點(diǎn)擊部署完成后會(huì)自動(dòng)彈出調(diào)用合約的界面,之前生成的腳本會(huì)自動(dòng)顯示在上方的文本框中揽思。
這里必須先點(diǎn)擊試運(yùn)行,當(dāng)試運(yùn)行通過之后才可以點(diǎn)擊調(diào)用渊鞋。
點(diǎn)擊試運(yùn)行會(huì)調(diào)用一下代碼:
private void button5_Click(object sender, EventArgs e)
{
byte[] script;
try
{
script = textBox6.Text.Trim().HexToBytes();
}
catch (FormatException ex)
{
MessageBox.Show(ex.Message);
return;
}
if (tx == null) tx = new InvocationTransaction();
tx.Version = 1;
tx.Script = script;
if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
if (tx.Inputs == null) tx.Inputs = new CoinReference[0];
if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
if (tx.Scripts == null) tx.Scripts = new Witness[0];
ApplicationEngine engine = ApplicationEngine.Run(tx.Script, tx);
StringBuilder sb = new StringBuilder();
sb.AppendLine($"VM State: {engine.State}");
sb.AppendLine($"Gas Consumed: {engine.GasConsumed}");
sb.AppendLine($"Evaluation Stack: {new JArray(engine.EvaluationStack.Select(p => p.ToParameter().ToJson()))}");
textBox7.Text = sb.ToString();
if (!engine.State.HasFlag(VMState.FAULT))
{
tx.Gas = engine.GasConsumed - Fixed8.FromDecimal(10);
if (tx.Gas < Fixed8.Zero) tx.Gas = Fixed8.Zero;
tx.Gas = tx.Gas.Ceiling();
Fixed8 fee = tx.Gas.Equals(Fixed8.Zero) ? net_fee : tx.Gas;
label7.Text = fee + " gas";
button3.Enabled = true;
}
else
{
MessageBox.Show(Strings.ExecutionFailed);
}
}
ApplicationEngine.Run(tx.Script, tx);此時(shí)會(huì)將tx放入虛擬機(jī)中進(jìn)行運(yùn)行绰更。
public static ApplicationEngine Run(byte[] script, IScriptContainer container = null, Block persisting_block = null)
{
TR.Enter();
if (persisting_block == null)
persisting_block = new Block
{
Version = 0,
PrevHash = Blockchain.Default.CurrentBlockHash,
MerkleRoot = new UInt256(),
Timestamp = Blockchain.Default.GetHeader(Blockchain.Default.Height).Timestamp + Blockchain.SecondsPerBlock,
Index = Blockchain.Default.Height + 1,
ConsensusData = 0,
NextConsensus = Blockchain.Default.GetHeader(Blockchain.Default.Height).NextConsensus,
Script = new Witness
{
InvocationScript = new byte[0],
VerificationScript = new byte[0]
},
Transactions = new Transaction[0]
};
DataCache<UInt160, AccountState> accounts = Blockchain.Default.GetStates<UInt160, AccountState>();
DataCache<UInt256, AssetState> assets = Blockchain.Default.GetStates<UInt256, AssetState>();
DataCache<UInt160, ContractState> contracts = Blockchain.Default.GetStates<UInt160, ContractState>();
DataCache<StorageKey, StorageItem> storages = Blockchain.Default.GetStates<StorageKey, StorageItem>();
CachedScriptTable script_table = new CachedScriptTable(contracts);
using (StateMachine service = new StateMachine(persisting_block, accounts, assets, contracts, storages))
{
ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, script_table, service, Fixed8.Zero, true);
engine.LoadScript(script, false);
engine.Execute();
return TR.Exit(engine);
}
}
首先會(huì)注冊service,然后加載腳本至虛擬機(jī)瞧挤,運(yùn)行虛擬機(jī)。
public new bool Execute()
{
TR.Enter();
try
{
while (!State.HasFlag(VMState.HALT) && !State.HasFlag(VMState.FAULT))
{
if (CurrentContext.InstructionPointer < CurrentContext.Script.Length)
{
OpCode nextOpcode = CurrentContext.NextInstruction;
gas_consumed = checked(gas_consumed + GetPrice(nextOpcode) * ratio);
if (!testMode && gas_consumed > gas_amount)
{
State |= VMState.FAULT;
return TR.Exit(false);
}
if (!CheckItemSize(nextOpcode) ||
!CheckStackSize(nextOpcode) ||
!CheckArraySize(nextOpcode) ||
!CheckInvocationStack(nextOpcode) ||
!CheckBigIntegers(nextOpcode) ||
!CheckDynamicInvoke(nextOpcode))
{
State |= VMState.FAULT;
return TR.Exit(false);
}
}
StepInto();
}
}
catch
{
State |= VMState.FAULT;
return TR.Exit(false);
}
return TR.Exit(!State.HasFlag(VMState.FAULT));
}
這部分運(yùn)行和虛擬機(jī)的運(yùn)行基本類似儡湾,只是多了一步計(jì)算gas消耗的操作特恬,GetPrice(),官方規(guī)定了不同操作收取不同的費(fèi)用 http://docs.neo.org/zh-cn/sc/systemfees.html徐钠。
其中大部分操作有固定的費(fèi)用癌刽,其他操作根據(jù)指令有不同情況。對于多簽驗(yàn)證尝丐,每個(gè)簽名收取0.1gas(應(yīng)該是一個(gè)公鑰0.1gas?)显拜。
case OpCode.CHECKMULTISIG:
{
if (EvaluationStack.Count == 0) return TR.Exit(1);
int n = (int)EvaluationStack.Peek().GetBigInteger();
if (n < 1) return TR.Exit(1);
return TR.Exit(100 * n);
}
對于Contract.Create和Contract.Migrate類型,ContractProperties位于contract第四的位置爹袁,所以將contract_properties找出來远荠,看是否需要存儲和動(dòng)態(tài)調(diào)用, 創(chuàng)建智能合約與遷移智能合約目前是根據(jù)合約所需功能進(jìn)行收費(fèi)失息。其中基礎(chǔ)的費(fèi)用為 100GAS譬淳,需要存儲區(qū) +400GAS,需要?jiǎng)討B(tài)調(diào)用 +500GAS盹兢。
case "AntShares.Contract.Migrate":
long fee = 100L;
ContractPropertyState contract_properties = (ContractPropertyState)(byte)EvaluationStack.Peek(3).GetBigInteger();
if (contract_properties.HasFlag(ContractPropertyState.HasStorage))
{
fee += 400L;
}
if (contract_properties.HasFlag(ContractPropertyState.HasDynamicInvoke))
{
fee += 500L;
}
return TR.Exit(fee * 100000000L / ratio);
運(yùn)行完成后會(huì)返回engine,然后將相關(guān)信息顯示
StringBuilder sb = new StringBuilder();
sb.AppendLine($"VM State: {engine.State}");
sb.AppendLine($"Gas Consumed: {engine.GasConsumed}");
sb.AppendLine($"Evaluation Stack: {new JArray(engine.EvaluationStack.Select(p => p.ToParameter().ToJson()))}");
textBox7.Text = sb.ToString();
if (!engine.State.HasFlag(VMState.FAULT))
{
tx.Gas = engine.GasConsumed - Fixed8.FromDecimal(10);
if (tx.Gas < Fixed8.Zero) tx.Gas = Fixed8.Zero;
tx.Gas = tx.Gas.Ceiling();
Fixed8 fee = tx.Gas.Equals(Fixed8.Zero) ? net_fee : tx.Gas;
label7.Text = fee + " gas";
button3.Enabled = true;
}
tx的gas消耗為所有操作的gas消耗綜合減去10gas的免費(fèi)額度后的值邻梆。并將調(diào)用按鈕變?yōu)榭捎谩?/p>
點(diǎn)擊調(diào)用后,由tx構(gòu)建一個(gè)InvocationTransaction绎秒,調(diào)用了MakeTransaction<T>浦妄。
public T MakeTransaction<T>(T tx, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) where T : Transaction
{
TR.Enter();
if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
fee += tx.SystemFee; // tx.SystemFee = tx.Gas
var pay_total = (typeof(T) == typeof(IssueTransaction) ? new TransactionOutput[0] : tx.Outputs).GroupBy(p => p.AssetId, (k, g) => new
{
AssetId = k,
Value = g.Sum(p => p.Value)
}).ToDictionary(p => p.AssetId);
if (fee > Fixed8.Zero)
{
if (pay_total.ContainsKey(Blockchain.UtilityToken.Hash))
{
pay_total[Blockchain.UtilityToken.Hash] = new
{
AssetId = Blockchain.UtilityToken.Hash,
Value = pay_total[Blockchain.UtilityToken.Hash].Value + fee
};
}
else
{
pay_total.Add(Blockchain.UtilityToken.Hash, new
{
AssetId = Blockchain.UtilityToken.Hash,
Value = fee
});
}
}
var pay_coins = pay_total.Select(p => new
{
AssetId = p.Key,
Unspents = from == null ? FindUnspentCoins(p.Key, p.Value.Value) : FindUnspentCoins(p.Key, p.Value.Value, from)
}).ToDictionary(p => p.AssetId);
if (pay_coins.Any(p => p.Value.Unspents == null)) return null;
var input_sum = pay_coins.Values.ToDictionary(p => p.AssetId, p => new
{
p.AssetId,
Value = p.Unspents.Sum(q => q.Output.Value)
});
if (change_address == null) change_address = GetChangeAddress();
List<TransactionOutput> outputs_new = new List<TransactionOutput>(tx.Outputs);
foreach (UInt256 asset_id in input_sum.Keys)
{
if (input_sum[asset_id].Value > pay_total[asset_id].Value)
{
outputs_new.Add(new TransactionOutput
{
AssetId = asset_id,
Value = input_sum[asset_id].Value - pay_total[asset_id].Value,
ScriptHash = change_address
});
}
}
tx.Inputs = pay_coins.Values.SelectMany(p => p.Unspents).Select(p => p.Reference).ToArray();
tx.Outputs = outputs_new.ToArray();
return TR.Exit(tx);
}
paytotal = gas,會(huì)根據(jù)這個(gè)新建一個(gè)outputs.之后得到一個(gè)完整的tx见芹。
- SignAndShowInformation(tx) 這部分與之前講的一樣剂娄。
public static void SignAndShowInformation(Transaction tx)
{
if (tx == null)
{
MessageBox.Show(Strings.InsufficientFunds);
return;
}
ContractParametersContext context;
try
{
context = new ContractParametersContext(tx);
}
catch (InvalidOperationException)
{
MessageBox.Show(Strings.UnsynchronizedBlock);
return;
}
Program.CurrentWallet.Sign(context); //簽名
if (context.Completed) //如果簽名完成
{
context.Verifiable.Scripts = context.GetScripts();
Program.CurrentWallet.ApplyTransaction(tx);
Program.LocalNode.Relay(tx); //廣播至其他節(jié)點(diǎn)
InformationBox.Show(tx.Hash.ToString(), Strings.SendTxSucceedMessage, Strings.SendTxSucceedTitle);
}
else
{
InformationBox.Show(context.ToString(), Strings.IncompletedSignatureMessage, Strings.IncompletedSignatureTitle);
}
}
首先是簽名部分,
public bool Sign(ContractParametersContext context)
{
TR.Enter();
bool fSuccess = false;
foreach (UInt160 scriptHash in context.ScriptHashes) // 找到交易所有輸入對應(yīng)的地址
{
WalletAccount account = GetAccount(scriptHash); // 查看錢包是否有對應(yīng)的賬戶地址
if (account?.HasKey != true) continue;
KeyPair key = account.GetKey(); //獲取賬戶秘鑰對
byte[] signature = context.Verifiable.Sign(key); //簽名
fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); //將簽名添加到參數(shù)表中
}
return TR.Exit(fSuccess);
}
主要是AddSignature()
public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature)
{
TR.Enter();
if (contract.IsMultiSigContract()) //判斷多簽
{
ContextItem item = CreateItem(contract);
if (item == null) return TR.Exit(false);
if (item.Parameters.All(p => p.Value != null)) return TR.Exit(false);
if (item.Signatures == null)
item.Signatures = new Dictionary<ECPoint, byte[]>();
else if (item.Signatures.ContainsKey(pubkey))
return TR.Exit(false);
List<ECPoint> points = new List<ECPoint>(); //需要簽名的地址列表
{
int i = 0;
switch (contract.Script[i++])
{
case 1:
++i;
break;
case 2:
i += 2;
break;
}
while (contract.Script[i++] == 33)
{
points.Add(ECPoint.DecodePoint(contract.Script.Skip(i).Take(33).ToArray(), ECCurve.Secp256r1));
i += 33;
}
}
if (!points.Contains(pubkey)) return TR.Exit(false); //檢測是不是該這個(gè)用戶簽這個(gè)簽名
item.Signatures.Add(pubkey, signature);
if (item.Signatures.Count == contract.ParameterList.Length)
{
Dictionary<ECPoint, int> dic = points.Select((p, i) => new
{
PublicKey = p,
Index = i
}).ToDictionary(p => p.PublicKey, p => p.Index);
byte[][] sigs = item.Signatures.Select(p => new
{
Signature = p.Value,
Index = dic[p.Key]
}).OrderByDescending(p => p.Index).Select(p => p.Signature).ToArray();
for (int i = 0; i < sigs.Length; i++) //按照順序依次插入簽名玄呛。
if (!Add(contract, i, sigs[i]))
throw new InvalidOperationException();
item.Signatures = null;
}
return TR.Exit(true);
}
else
{
int index = -1;
for (int i = 0; i < contract.ParameterList.Length; i++)
if (contract.ParameterList[i] == ContractParameterType.Signature)
if (index >= 0)
throw new NotSupportedException();
else
index = i;
if(index == -1) {
// unable to find ContractParameterType.Signature in contract.ParameterList
// return now to prevent array index out of bounds exception
return TR.Exit(false);
}
return TR.Exit(Add(contract, index, signature));
}
}
首先判斷是否是多簽:
public virtual bool IsMultiSigContract()
{
TR.Enter();
int m, n = 0;
int i = 0;
if (Script.Length < 37) return TR.Exit(false); //m一個(gè)字節(jié)+push公鑰一個(gè)字節(jié)+最少一個(gè)公鑰+n一個(gè)字節(jié)+CHECKMULTISIG一個(gè)字節(jié)宜咒,最少是37字節(jié)。
if (Script[i] > (byte)OpCode.PUSH16) return TR.Exit(false);
if (Script[i] < (byte)OpCode.PUSH1 && Script[i] != 1 && Script[i] != 2) return TR.Exit(false);
switch (Script[i])
{
case 1:
m = Script[++i];
++i;
break;
case 2:
m = Script.ToUInt16(++i);
i += 2;
break;
default:
m = Script[i++] - 80;
break;
}
if (m < 1 || m > 1024) return TR.Exit(false);
while (Script[i] == 33)
{
i += 34;
if (Script.Length <= i) return TR.Exit(false);
++n;
}
if (n < m || n > 1024) return TR.Exit(false);
switch (Script[i])
{
case 1:
if (n != Script[++i]) return TR.Exit(false);
++i;
break;
case 2:
if (n != Script.ToUInt16(++i)) return TR.Exit(false);
i += 2;
break;
default:
if (n != Script[i++] - 80) return TR.Exit(false);
break;
}
if (Script[i++] != (byte)OpCode.CHECKMULTISIG) return TR.Exit(false);
if (Script.Length != i) return TR.Exit(false);
return TR.Exit(true);
}
如果是多簽把鉴,則首先獲取所有需要簽名的地址列表,然后檢測是否有需要該用戶簽名的儿咱,如果是庭砍,則把簽名添加到簽名列表中。當(dāng)所有簽名完畢時(shí)混埠,對所有簽名排序怠缸。
如果是單簽,則找到參數(shù)列表中簽名參數(shù)所在的下標(biāo)钳宪,將簽名 signature 加入到合約的參數(shù)變量列表里面揭北。
隨后會(huì)判斷簽名是否已經(jīng)完成扳炬,如果多簽簽名數(shù)不滿足條件,則需要其他賬戶繼續(xù)簽名搔体;如果滿足條件恨樟,則根據(jù)參數(shù)和腳本構(gòu)建witness,將交易發(fā)送至其他節(jié)點(diǎn)疚俱。到此便完成了一個(gè)智能合約的部署劝术。
2.智能合約的調(diào)用
這里主要介紹利用Script hash進(jìn)行函數(shù)調(diào)用。在gui中打開函數(shù)調(diào)用呆奕,如下圖养晋,輸入之前我們生成合約時(shí)的Script hash,然后點(diǎn)擊搜索的按鈕梁钾。
之后的代碼如下:
private void button1_Click(object sender, EventArgs e)
{
script_hash = UInt160.Parse(textBox1.Text);
ContractState contract = Blockchain.Default.GetContract(script_hash);
if (contract == null) return;
parameters = contract.ParameterList.Select(p => new ContractParameter(p)).ToArray();
textBox2.Text = contract.Name;
textBox3.Text = contract.CodeVersion;
textBox4.Text = contract.Author;
textBox5.Text = string.Join(", ", contract.ParameterList);
button2.Enabled = parameters.Length > 0;
UpdateScript();
}
這里調(diào)用Blockchain.Default.GetContract(script_hash)绳泉,通過合約的哈希去數(shù)據(jù)庫中尋找對應(yīng)的合約。
public override ContractState GetContract(UInt160 hash)
{
TR.Enter();
return TR.Exit(db.TryGet<ContractState>(ReadOptions.Default, DataEntryPrefix.ST_Contract, hash));
}
返回一個(gè)ContractState 姆泻,然后將部分信息顯示出來零酪。這里可以給參數(shù)列表賦值。例如
每次更新參數(shù)的值都會(huì)執(zhí)行下面的代碼:
private void button1_Click(object sender, EventArgs e)
{
if (listView1.SelectedIndices.Count == 0) return;
ContractParameter parameter = (ContractParameter)listView1.SelectedItems[0].Tag;
try
{
parameter.SetValue(textBox2.Text);
listView1.SelectedItems[0].SubItems["value"].Text = parameter.ToString();
textBox1.Text = listView1.SelectedItems[0].SubItems["value"].Text;
textBox2.Clear();
}
catch(Exception err)
{
MessageBox.Show(err.Message);
}
}
重點(diǎn)是SetValue:
public void SetValue(string text)
{
TR.Enter();
switch (Type)
{
case ContractParameterType.Signature:
byte[] signature = text.HexToBytes();
if (signature.Length != 64) throw new FormatException();
Value = signature;
break;
case ContractParameterType.Boolean:
Value = string.Equals(text, bool.TrueString, StringComparison.OrdinalIgnoreCase);
break;
case ContractParameterType.Integer:
Value = BigInteger.Parse(text);
break;
case ContractParameterType.Hash160:
Value = UInt160.Parse(text);
break;
case ContractParameterType.Hash256:
Value = UInt256.Parse(text);
break;
case ContractParameterType.ByteArray:
Value = text.HexToBytes();
break;
case ContractParameterType.PublicKey:
Value = ECPoint.Parse(text, ECCurve.Secp256r1);
break;
case ContractParameterType.String:
Value = text;
break;
default:
throw new ArgumentException();
}
TR.Exit();
}
之后合約的字節(jié)碼會(huì)變成這樣:
分開看如下圖:
點(diǎn)擊試運(yùn)行后過程與部署合約部分相同麦射,最后可以看到試運(yùn)行結(jié)果是一個(gè)ByteArray,值為helloworld蛾娶。
3.智能合約消耗GAS的處理
部署和調(diào)用智能合約都是作為一筆交易發(fā)送到其他節(jié)點(diǎn)的,其中交易的SystemFee值為試運(yùn)行過程中計(jì)算出的gas消耗量潜秋。Gas的值最終會(huì)寫入?yún)^(qū)塊當(dāng)中蛔琅,并且在生成gas的claimgas中會(huì)計(jì)算指定高度區(qū)間的系統(tǒng)費(fèi)用總量。
public T MakeTransaction<T>(T tx, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) where T : Transaction
{
TR.Enter();
if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
fee += tx.SystemFee; // SystemFee在合約交易中等于gas消耗費(fèi)用
var pay_total = (typeof(T) == typeof(IssueTransaction) ? new TransactionOutput[0] : tx.Outputs).GroupBy(p => p.AssetId, (k, g) => new
{
AssetId = k,
Value = g.Sum(p => p.Value)
}).ToDictionary(p => p.AssetId);
if (fee > Fixed8.Zero) // gas消耗大于0峻呛,則添加進(jìn)pay_total
{
if (pay_total.ContainsKey(Blockchain.UtilityToken.Hash))
{
pay_total[Blockchain.UtilityToken.Hash] = new
{
AssetId = Blockchain.UtilityToken.Hash,
Value = pay_total[Blockchain.UtilityToken.Hash].Value + fee
};
}
else
{
pay_total.Add(Blockchain.UtilityToken.Hash, new
{
AssetId = Blockchain.UtilityToken.Hash,
Value = fee
});
}
}
var pay_coins = pay_total.Select(p => new
{
AssetId = p.Key,
Unspents = from == null ? FindUnspentCoins(p.Key, p.Value.Value) : FindUnspentCoins(p.Key, p.Value.Value, from)
}).ToDictionary(p => p.AssetId);
if (pay_coins.Any(p => p.Value.Unspents == null)) return null;
var input_sum = pay_coins.Values.ToDictionary(p => p.AssetId, p => new
{
p.AssetId,
Value = p.Unspents.Sum(q => q.Output.Value)
});
if (change_address == null) change_address = GetChangeAddress();
List<TransactionOutput> outputs_new = new List<TransactionOutput>(tx.Outputs);
foreach (UInt256 asset_id in input_sum.Keys)
{
if (input_sum[asset_id].Value > pay_total[asset_id].Value)
{
outputs_new.Add(new TransactionOutput
{
AssetId = asset_id,
Value = input_sum[asset_id].Value - pay_total[asset_id].Value,
ScriptHash = change_address
});
}
}
tx.Inputs = pay_coins.Values.SelectMany(p => p.Unspents).Select(p => p.Reference).ToArray();
tx.Outputs = outputs_new.ToArray();
return TR.Exit(tx);
}
共識節(jié)點(diǎn)處理交易時(shí)
private void FillContext()
{
TR.Enter();
IEnumerable<Transaction> mem_pool = LocalNode.GetMemoryPool().Where(p => CheckPolicy(p));
foreach (PolicyPlugin plugin in PolicyPlugin.Instances)
mem_pool = plugin.Filter(mem_pool);
List<Transaction> transactions = mem_pool.ToList();
Fixed8 amount_netfee = Block.CalculateNetFee(transactions);
TransactionOutput[] outputs = amount_netfee == Fixed8.Zero ? new TransactionOutput[0] : new[] { new TransactionOutput
{
AssetId = Blockchain.UtilityToken.Hash,
Value = amount_netfee,
ScriptHash = wallet.GetChangeAddress()
} };
while (true)
{
ulong nonce = GetNonce();
MinerTransaction tx = new MinerTransaction
{
Nonce = (uint)(nonce % (uint.MaxValue + 1ul)),
Attributes = new TransactionAttribute[0],
Inputs = new CoinReference[0],
Outputs = outputs,
Scripts = new Witness[0]
};
if (Blockchain.Default.GetTransaction(tx.Hash) == null)
{
context.Nonce = nonce;
transactions.Insert(0, tx);
break;
}
}
context.TransactionHashes = transactions.Select(p => p.Hash).ToArray();
context.Transactions = transactions.ToDictionary(p => p.Hash);
context.NextConsensus = Blockchain.GetConsensusAddress(Blockchain.Default.GetValidators(transactions).ToArray());
TR.Exit();
}
這里的amount_netfee是網(wǎng)絡(luò)費(fèi)用的數(shù)量罗售,計(jì)算方法為amount_in - amount_out - amount_sysfee:
public static Fixed8 CalculateNetFee(IEnumerable<Transaction> transactions)
{
TR.Enter();
Transaction[] ts = transactions.Where(p => p.Type != TransactionType.MinerTransaction && p.Type != TransactionType.ClaimTransaction).ToArray();
Fixed8 amount_in = ts.SelectMany(p => p.References.Values.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value);
Fixed8 amount_out = ts.SelectMany(p => p.Outputs.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value);
Fixed8 amount_sysfee = ts.Sum(p => p.SystemFee);
return TR.Exit(amount_in - amount_out - amount_sysfee);
}
在claimgas當(dāng)中,最后一步會(huì)加上系統(tǒng)費(fèi)用钩述,相當(dāng)于最后把系統(tǒng)費(fèi)用分給了所有neo持有者寨躁。
private static Fixed8 CalculateBonusInternal(IEnumerable<SpentCoin> unclaimed)
{
TR.Enter();
Fixed8 amount_claimed = Fixed8.Zero;
foreach (var group in unclaimed.GroupBy(p => new { p.StartHeight, p.EndHeight }))
{
uint amount = 0;
uint ustart = group.Key.StartHeight / DecrementInterval;
if (ustart < GenerationAmount.Length)
{
uint istart = group.Key.StartHeight % DecrementInterval;
uint uend = group.Key.EndHeight / DecrementInterval;
uint iend = group.Key.EndHeight % DecrementInterval;
if (uend >= GenerationAmount.Length)
{
uend = (uint)GenerationAmount.Length;
iend = 0;
}
if (iend == 0)
{
uend--;
iend = DecrementInterval;
}
while (ustart < uend)
{
amount += (DecrementInterval - istart) * GenerationAmount[ustart];
ustart++;
istart = 0;
}
amount += (iend - istart) * GenerationAmount[ustart];
}
amount += (uint)(Default.GetSysFeeAmount(group.Key.EndHeight - 1) - (group.Key.StartHeight == 0 ? 0 : Default.GetSysFeeAmount(group.Key.StartHeight - 1)));
amount_claimed += group.Sum(p => p.Value) / 100000000 * amount;
}
return TR.Exit(amount_claimed);
}