gRPC API questions

Hi Petlan,

I’ve filled out the missing parts of your JSON example and added some comments.
It should give a better understanding of what the gRPC API expects.

{
   "Version":0, // Variable length big-endian base-128 encoding (LEB128)
   "Tag":0, // Unsigned 8bit integer
   "AccountTransaction":{
      "TransactionSignature":{
         "CredentialsMapLength":2, // Unsigned 8bit integer (is 2, because there are two entries in OuterMap).
          "CredentialsMap": [
              {
                "CredentialIndex": 0, // Unsigned 8bit integer
                "KeysMapLength": 1, // Unsigned 8bit integer (is 1, because there is one entry in InnerMap).
                "KeysMap": [
                    {
                      "KeyIndex": 0, // Unsigned 8bit integer
                      "LengthOfSignature": 10, // Unsigned 16bit integer (big-endian)
                      "Signature": [0,1,2,3,4,5,6,7,8,9] // A signature as a bytearray
                    }
                ]
              },
              { "CredentialIndex": 1, // Unsigned 8bit integer
                "KeysMapLength": 2, // Unsigned 8bit integer (is 2, because there are two entries in the InnerMap)
                "KeysMap": [
                    {
                      "KeyIndex": 0, // Unsigned 8bit integer
                      "LengthOfSignature": 5, // Unsigned 16bit integer (big-endian)
                      "Signature": [0,1,2,3,4] // A signature as a bytearray
                    }
                    {
                      "KeyIndex": 1, // Unsigned 8bit integer
                      "LengthOfSignature": 10, // Unsigned 16bit integer (big-endian)
                      "Signature": [0,1,2,3,4,5,6,7,8,9] // A signature as a bytearray
                    }
                ]
              }
          ]
      },
      "TransactionHeader":{
         "AccountAddress": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31], // Byte array of size 32
         "Nonce":1, // Unsigned 64bit integer (big-endian)
         "Energy":1000, // Unsigned 64bit integer (big-endian)
         "PayloadSize":0, // Unsigned 32bit integer (big-endian)
         "TransactionExpiryTime":1647426688 // Unsigned 64bit integer (big-endian)
      },
      "Payload":{
         "Tag":1, // Unsigned 8bit integer
         "Content":{
            "Amount":0, // Unsigned 64bit integer (big-endian)
             "ModuleRef": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31], // Byte array with fixed size of 32.
            "InitName":{
               "Length":14, // Unsigned 16bit integer (big-endian)
               "Name": [105, 110, 105, 116, 95, 80, 105, 103, 103, 121, 66, 97, 110, 107], // "init_PiggyBank" as a UTF-8 encoded byte array
            },
            "Parameter":{
                "Length": 0, // Unsigned 16bit integer (big-endian)
                "Bytes": [], // Bytearray of the parameter accessible with ctx.parameter.get() in the contract. No parameter is needed for the piggy bank.
            }
         }
      }
   }
}

I also went ahead and implemented the serialization of the init payload in C# so that you can get the idea.
The built-in C# libraries use either only little-endianness, or they use the endianness of the machine. So to encode the numbers in big-endian, I used the BinarySerialization package.

I just print the bytes in the example, but you should, of course, send it through gRPC instead :blush:

You can see and run the example on Replit.

using System;
using System.IO;
using BinarySerialization;

class Program
{
    public static void Main(string[] args)
    {
        byte[] data;
        var initPayload = new InitPayload(new Amount(42), 
                                          new ModuleRef(new byte[] {99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68}),
                                          new InitName("init_PiggyBank"), 
                                          new Parameter(new byte[] {}));
        using(var stream = new MemoryStream()) {
          var serializer = new BinarySerializer();
          serializer.Serialize(stream, initPayload);
          data = stream.ToArray();
        }
      Console.WriteLine(string.Join(", ", data));
    }
}

class InitPayload
{
  [FieldOrder(0)]
  public Byte Tag {get;} = 1;
  
  [FieldOrder(1)]
  public Amount Amount {get;}

  [FieldOrder(2)]
  public ModuleRef ModuleRef {get;}

  [FieldOrder(3)]
  public InitName InitName {get;}

  [FieldOrder(4)]
  public Parameter Parameter {get;}

  public InitPayload(Amount amount, ModuleRef moduleRef, InitName initName, Parameter parameter) {
    this.Amount = amount;
    this.ModuleRef = moduleRef;
    this.InitName = initName;
    this.Parameter = parameter;
  }
}

class Amount
{
  [FieldOrder(0)]
  [FieldEndianness(BinarySerialization.Endianness.Big)]
  public UInt64 Amount_ {get;}
  
  public Amount(UInt64 amount) {
    this.Amount_ = amount;
  }
}

class ModuleRef 
{
  [FieldOrder(0)]
  [FieldLength(32)]
  public byte[] ModuleRef_ {get;}
  
  public ModuleRef(byte[] moduleRef){
    if(moduleRef.Length != 32) {
      throw new ArgumentException("The module reference must be 32 bytes long.");
    }
    this.ModuleRef_ = moduleRef;
  }
}

class InitName
{
  [FieldOrder(0)]
  [FieldEndianness(BinarySerialization.Endianness.Big)]
  public UInt16 Length {get;}
  
  [FieldOrder(1)]
  [FieldLength(nameof(Length))]
  public String InitName_ {get;} // Strings are UTF16 by default, but since it is an initname, which is ASCII, it should not be a problem.
  
  public InitName(String initName) {
    this.InitName_ = initName;
  }
}

class Parameter
{
  [FieldOrder(0)]
  [FieldEndianness(BinarySerialization.Endianness.Big)]
  public UInt16 Length {get; set;}
  
  [FieldOrder(1)]
  [FieldLength(nameof(Length))] // This sets the length field automatically.
  public byte[] Parameter_ {get;}

  public Parameter(byte[] parameter) {
    this.Parameter_ = parameter;
  }
}

This is what it outputs (I’ve split it up and added comments):

1, <- tag
0, 0, 0, 0, 0, 0, 0, 42, <- amount
99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, <- moduleRef
0, 14, <- InitName Length
105, 110, 105, 116, 95, 80, 105, 103, 103, 121, 66, 97, 110, 107, <- InitName
0, 0 <- Parameter length

If you included a non-empty parameter, it would look like this:

...
0, 3, <- Parameter length
99, 98, 97 <- Parameter bytes

So that is where “the bytes” would go :wink:

The next steps for you would be to take the code from my example and expand it with classes for TransactionHeader, AccountTransaction, and BlockItem.

I hope that makes sense, otherwise let me know :blush:

Best regards,
Kasper

Hi Kasper,

Thank you for taking the time to do this. I will take a look at it for sure :slight_smile: