NEO的智能合约部署与调用
1.智能合约的部署
- 首先在gui里加载已经编写好的合约avm,然后填入相关信息以及参数列表和返回值。这里我们的合约输入是两个string,输出为一个string,所以参数列表填入0707,返回值07。
snipaste_20181018_182245.png - 调用GetTransaction(),其作用为从gui里读出合约相关信息,然后根据信息创建一个合约脚本。
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后面会在加载虚拟机时执行下面的方法构造一个智能合约。
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);
}
最后返回了一个InvocationTransaction,其Script包含合约的信息。
textBox9.Text = textBox8.Text.HexToBytes().ToScriptHash().ToString();
即Script Hash的值为智能合约代码的hash值,后面合约调用也是根据这个hash区寻找指定的脚本。这里可能会造成一个问题,如果你和别人的智能合约代码完全相同,则这两个脚本会指向同一个地址,可能会出现异常。
-
点击部署完成后会自动弹出调用合约的界面,之前生成的脚本会自动显示在上方的文本框中。
snipaste_20181019_121437.png
这里必须先点击试运行,当试运行通过之后才可以点击调用。
点击试运行会调用一下代码:
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);此时会将tx放入虚拟机中进行运行。
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);
}
}
首先会注册service,然后加载脚本至虚拟机,运行虚拟机。
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));
}
这部分运行和虚拟机的运行基本类似,只是多了一步计算gas消耗的操作,GetPrice(),官方规定了不同操作收取不同的费用 http://docs.neo.org/zh-cn/sc/systemfees.html。
其中大部分操作有固定的费用,其他操作根据指令有不同情况。对于多签验证,每个签名收取0.1gas(应该是一个公钥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找出来,看是否需要存储和动态调用, 创建智能合约与迁移智能合约目前是根据合约所需功能进行收费。其中基础的费用为 100GAS,需要存储区 +400GAS,需要动态调用 +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);
运行完成后会返回engine,然后将相关信息显示
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的免费额度后的值。并将调用按钮变为可用。
snipaste_20181019_141410.png
点击调用后,由tx构建一个InvocationTransaction,调用了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,会根据这个新建一个outputs.之后得到一个完整的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); //广播至其他节点
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) // 找到交易所有输入对应的地址
{
WalletAccount account = GetAccount(scriptHash); // 查看钱包是否有对应的账户地址
if (account?.HasKey != true) continue;
KeyPair key = account.GetKey(); //获取账户秘钥对
byte[] signature = context.Verifiable.Sign(key); //签名
fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); //将签名添加到参数表中
}
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); //检测是不是该这个用户签这个签名
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));
}
}
首先判断是否是多签:
snipaste_20181022_183129.png
public virtual bool IsMultiSigContract()
{
TR.Enter();
int m, n = 0;
int i = 0;
if (Script.Length < 37) return TR.Exit(false); //m一个字节+push公钥一个字节+最少一个公钥+n一个字节+CHECKMULTISIG一个字节,最少是37字节。
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);
}
如果是多签,则首先获取所有需要签名的地址列表,然后检测是否有需要该用户签名的,如果是,则把签名添加到签名列表中。当所有签名完毕时,对所有签名排序。
如果是单签,则找到参数列表中签名参数所在的下标,将签名 signature 加入到合约的参数变量列表里面。
随后会判断签名是否已经完成,如果多签签名数不满足条件,则需要其他账户继续签名;如果满足条件,则根据参数和脚本构建witness,将交易发送至其他节点。到此便完成了一个智能合约的部署。
snipaste_20181024_154912.png
2.智能合约的调用
这里主要介绍利用Script hash进行函数调用。在gui中打开函数调用,如下图,输入之前我们生成合约时的Script hash,然后点击搜索的按钮。
snipaste_20181019_161158.png
之后的代码如下:
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();
}
这里调用Blockchain.Default.GetContract(script_hash),通过合约的哈希去数据库中寻找对应的合约。
public override ContractState GetContract(UInt160 hash)
{
TR.Enter();
return TR.Exit(db.TryGet<ContractState>(ReadOptions.Default, DataEntryPrefix.ST_Contract, hash));
}
返回一个ContractState ,然后将部分信息显示出来。这里可以给参数列表赋值。例如
snipaste_20181019_163640.png
每次更新参数的值都会执行下面的代码:
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);
}
}
重点是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();
}
之后合约的字节码会变成这样:
snipaste_20181022_135128.png
分开看如下图:
snipaste_20181022_141256.png
点击试运行后过程与部署合约部分相同,最后可以看到试运行结果是一个ByteArray,值为helloworld。
snipaste_20181022_144533.png
3.智能合约消耗GAS的处理
部署和调用智能合约都是作为一笔交易发送到其他节点的,其中交易的SystemFee值为试运行过程中计算出的gas消耗量。Gas的值最终会写入区块当中,并且在生成gas的claimgas中会计算指定高度区间的系统费用总量。
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消耗费用
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,则添加进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);
}
共识节点处理交易时
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是网络费用的数量,计算方法为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当中,最后一步会加上系统费用,相当于最后把系统费用分给了所有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);
}