Update: For anyone coming here looking for C# sample code it’s at https://gist.github.com/sipsorcery/9420892747104d72054e6504317d3b24.
I’ve been attempting to call SubmitTransaction
against the testnet grpc endpoint at ac.testnet.libra.org:8000
. To date I have not been able to get a transaction accepted into the Libra state. My C# sample code is at the end of this post.
The response to my call has AcStatus
as Accepted
which I believe indicated the signature on the transaction is good? But the submitted transaction never shows up in the state indicating there is probably something I’m doing wrong with the raw transaction serialisation.
As a separate test I have generated an unsigned RawTransaction
blob and successfully submitted it using the Libra cli with the submit
command:
libra% submit 9c147f48c31695955d78955766eb796ff300bc438b1ce79e465672fad09ca8cb /tmp/libra_tx.raw
I have a couple of questions:
- Are there any examples of how to call
SubmitTransaction
onac.testnet.libra.org:8000
? - Is there any plan to add something like
submitraw
to the Libra CLI? It’s invaluable for testing to be able to submit signed transactions against a local node.
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Google.Protobuf;
using Grpc.Core;
using NBitcoin.DataEncoders;
using NSec.Cryptography;
namespace LibraTestConsole
{
class Program
{
static void Main(string[] args)
{
try
{
HexEncoder hex = new HexEncoder();
SharedSecret sharedSecret = SharedSecret.Import(Encoding.UTF8.GetBytes("dummy"));
HkdfSha512 kdf = new HkdfSha512();
var key = kdf.DeriveKey(sharedSecret, null, null, Ed25519.Ed25519);
var sender = key.PublicKey.Export(KeyBlobFormat.RawPublicKey);
Channel channel = new Channel("ac.testnet.libra.org:8000", ChannelCredentials.Insecure);
var client = new AdmissionControl.AdmissionControl.AdmissionControlClient(channel);
UInt64 seqNum = 0;
string senderHex = hex.EncodeData(sender);
var rawTx = CreateRawTx(senderHex, seqNum, "9c147f48c31695955d78955766eb796ff300bc438b1ce79e465672fad09ca8cb", 1UL, 10000UL, 0, 0UL);
Console.WriteLine($"RawTx: {Convert.ToBase64String(rawTx.ToByteArray())}");
Types.SignedTransaction signedTx = new Types.SignedTransaction();
signedTx.SenderPublicKey = Google.Protobuf.ByteString.CopyFrom(sender);
signedTx.RawTxnBytes = rawTx.ToByteString();
var sig = NSec.Cryptography.Ed25519.Ed25519.Sign(key, signedTx.RawTxnBytes.Span);
signedTx.SenderSignature = Google.Protobuf.ByteString.CopyFrom(sig);
AdmissionControl.SubmitTransactionRequest submitTxReq = new AdmissionControl.SubmitTransactionRequest();
submitTxReq.SignedTxn = signedTx;
Console.WriteLine($"Submitting signed tx for {senderHex} and seqnum {seqNum}.");
var reply = client.SubmitTransaction(submitTxReq);
Console.WriteLine($"Reply AcStatus {reply.AcStatus}.");
GetTransaction(client, senderHex, seqNum);
}
catch (Exception excp)
{
Console.WriteLine($"Exception Main. {excp.Message}");
}
}
private static Types.RawTransaction CreateRawTx(string senderHex, UInt64 seqNum, string receipientHex, UInt64 recipientAmount, UInt64 maxGasAmount, UInt64 maxGasUnitPrice, UInt64 expirationTime)
{
HexEncoder hex = new HexEncoder();
Types.RawTransaction rawTx = new Types.RawTransaction();
rawTx.SenderAccount = Google.Protobuf.ByteString.CopyFrom(hex.DecodeData(senderHex));
rawTx.SequenceNumber = seqNum;
rawTx.Program = new Types.Program();
rawTx.Program.Code = Google.Protobuf.ByteString.CopyFrom(Convert.FromBase64String("TElCUkFWTQoBAAcBSgAAAAQAAAADTgAAAAYAAAAMVAAAAAUAAAANWQAAAAQAAAAFXQAAACkAAAAEhgAAACAAAAAHpgAAAA0AAAAAAAABAAIAAQMAAgACBAIDAgQCBjxTRUxGPgxMaWJyYUFjY291bnQEbWFpbg9wYXlfZnJvbV9zZW5kZXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgAEAAwADAERAQI="));
var recipientArg = new Types.TransactionArgument { Type = Types.TransactionArgument.Types.ArgType.Address };
recipientArg.Data = Google.Protobuf.ByteString.CopyFrom(hex.DecodeData(receipientHex));
rawTx.Program.Arguments.Add(recipientArg);
var amountArg = new Types.TransactionArgument { Type = Types.TransactionArgument.Types.ArgType.U64 };
amountArg.Data = Google.Protobuf.ByteString.CopyFrom(BitConverter.GetBytes(recipientAmount));
rawTx.Program.Arguments.Add(amountArg);
rawTx.MaxGasAmount = maxGasAmount;
rawTx.GasUnitPrice = maxGasUnitPrice;
rawTx.ExpirationTime = expirationTime;
return rawTx;
}
private static void GetTransaction(AdmissionControl.AdmissionControl.AdmissionControlClient client, string accountHex, UInt64 seqNum)
{
Console.WriteLine($"GetTransaction for {accountHex} and seqnum {seqNum}.");
HexEncoder hex = new HexEncoder();
Types.UpdateToLatestLedgerRequest updToLatestLedgerReq = new Types.UpdateToLatestLedgerRequest();
var getTxReq = new Types.GetAccountTransactionBySequenceNumberRequest();
getTxReq.SequenceNumber = seqNum;
getTxReq.Account = Google.Protobuf.ByteString.CopyFrom(hex.DecodeData(accountHex));
Types.RequestItem reqItem = new Types.RequestItem();
reqItem.GetAccountTransactionBySequenceNumberRequest = getTxReq;
updToLatestLedgerReq.RequestedItems.Add(reqItem);
var reply = client.UpdateToLatestLedger(updToLatestLedgerReq);
if (reply?.ResponseItems?.Count == 1)
{
var resp = reply.ResponseItems[0].GetAccountTransactionBySequenceNumberResponse;
if (resp.SignedTransactionWithProof == null)
{
Console.WriteLine("GetTransaction request did not return a signed transaction.");
}
else
{
var signedTx = resp.SignedTransactionWithProof;
Console.WriteLine($"Sender {hex.EncodeData(signedTx.SignedTransaction.SenderPublicKey.ToByteArray())}.");
Console.WriteLine($"RawTxnBytes {hex.EncodeData(signedTx.SignedTransaction.RawTxnBytes.ToByteArray())}");
Types.RawTransaction rawTx = Types.RawTransaction.Parser.ParseFrom(signedTx.SignedTransaction.RawTxnBytes);
Console.WriteLine($"SequenceNumber {rawTx.SequenceNumber}.");
Console.WriteLine($"MaxGasAmount {rawTx.MaxGasAmount}.");
Console.WriteLine($"GasUnitPrice {rawTx.GasUnitPrice}.");
Console.WriteLine($"ExpirationTime {rawTx.ExpirationTime}.");
var byteCode = rawTx.Program.Code.ToByteArray();
SHA512 sha512 = SHA512.Create();
var byteCodeHash = hex.EncodeData(sha512.ComputeHash(byteCode));
Console.WriteLine($"Program.Code hash {byteCodeHash}.");
}
}
else
{
Console.WriteLine("GetTransaction did not return a result.");
}
}
}
}