Protocol defined thanks to Protocol Buffers

I recently discovered a very nice tool that makes the protocol makers life easier. It behaves as protocol descriptor system as well as compiler to generate the needed protocol data structures to many languages such C++, Java, Python, Javascript, etc. In addition it generates accessors functions in order to ease the access to the structure data and raise exception when a wrong data is passed on.

Since I will work on integrating the uCoin server using Python and C++ later on, I was looking for a solution that can help every contributors to connect easily the protocol to any applications coded around the project including contributions.

Protocol Buffers is maintained by Google and used by thousand of their projects, it helps them to guarantee backward and forward compatibility when updates are necessary.

I have ported the uCoin protocol into the protocol buffers format (.proto file), you can find it here:

package uc.protocol;

// uCoin data structures mapped from descriptions found at
// https://github.com/ucoin-io/ucoin/blob/master/doc/Protocol.md

//
// Certification
//
// The generic word certification is to be used for describing
// certification from others, i.e. non-self certifications.
//
message Certification {
    // certification public key
    required bytes pubkey_from = 1;

    // public key whose identity is being certified
    required bytes pubkey_to = 2;

    // certification date
    required uint32 timestamp = 3;

    // certification signature
    required bytes signature = 4;
}

//
// Membership
//
// A member is represented by a public key he is supposed to be the
// owner. To be integrated in a community, the newcomer owner of the
// key has to express its will to integrate the Community.
//
message Membership {
    // current structure version
    required uint32 version = 1;

    // name of the currency
    required string currency = 2;

    // public key of the issuer
    required bytes issuer = 3;

    // create date of this message, timestamp, this date may be
    // different from signature's date
    required uint32 date = 4;

    // state used by membership field
    enum State {
	IN = 0;
	OUT = 1;
    }

    // membership message, value is either IN or OUT to express
    // whether a member wishes to opt-in or opt-out the community
    required State membership = 5;

    // identity to use for this public key
    required string userid = 6;

    // certification of identity date
    required uint32 certts = 7;

    extensions 100 to 199;
}

//
// Transaction
//
// Transaction is the support of money, it allows to materialize
// coins' ownership.
//
message Transaction {
    // current structure version
    required uint32 version = 1;

    // name of the currency
    required string currency = 2;

    message Issuer {
	// issuer public key
	required bytes pubkey = 1;

	// sequential inter
	required uint32 index = 2;
    }

    // list of public key, followed by a sequential integer
    repeated Issuer issuers = 3;

    message Input {
	// index
	required uint32 index = 1;

	enum Source {
	    T = 0;
	    D = 1;
	    F = 2;
	}

	// source of coin
	required Source source = 2;

	// a sha-1 hash fingerprint
	required bytes fingerprint = 3;

	// amount
	required uint32 amount = 4;
    }

    // list of linking issuers (via index) to coin sources
    repeated Input inputs = 4;

    message Output {
	// a sha-1 hash fingerprint
	required bytes fingerprint = 1;

	// amount
	required uint32 amount = 2;
    }

    // list of public keys and amounts allowed to them
    repeated Output outputs = 5;

    // signatures of issuers
    repeated bytes signatures = 6;

    extensions 100 to 199;
}

//
// Block
//
// A Block is a document gathering both:
// * Public key data in order to build a Web Of Trust (WoT) representation
// * Transaction data to identify money units & ownership
//
message Block {
    // document version
    required uint32 version = 1;

    // document type
    optional string type = 2 [default = "Block"];

    // currency name
    required string currency = 3;

    // arbitrary nonce value
    required uint32 nonce = 4;

    // keyblock number
    required uint32 number = 5;

    // generation date
    required uint32 date = 6;

    // last confirmed date
    required uint32 confirmed_date = 7;

    // universal dividend amount
    optional uint32 universal_dividend = 8;

    // fees amount
    required uint32 fees = 9;

    // public key of the block issuer
    required bytes issuer = 10;

    // previous keyblock fingerprint (sha-1)
    optional bytes previous_hash = 11;

    // previous keyblock issuer public key
    optional bytes previous_issuer = 12;

    // number of members in the WoT, this block included
    required uint32 members_count = 13;

    message Identity {
	required bytes pubkey = 1;
	required bytes signature = 2;
	required uint32 timestamp = 3;
	required string user_id = 5;
    }

    // new identities in the WoT
    repeated Identity identities = 14;

    message Joiner {
	required bytes pubkey = 1;
	required bytes signature = 2;
	required uint32 timestamp = 3;
	required uint32 certts = 4;
	required string user_id = 5;
    }

    // IN memberships
    repeated Joiner joiners = 15;

    message Leaver {
	required bytes pubkey = 1;
	required bytes signature = 2;
	required uint32 timestamp = 3;
    }

    // OUT memberships
    repeated Leaver leavers = 16;

    // Excluded members public key
    repeated bytes excluded = 17;

    // Certifications
    repeated Certification certifications = 18;

    // List of compact transactions
    repeated Transaction transactions = 19;

    extensions 100 to 199;
}

//
// Blockchain
//
// A Blockchain is a chaining of Blocks. Such a structure describes a
// WoT + Transactions over the time.
//
message Blockchain {
    // list of blocks
    repeated Block blocks = 1;
}

//
// Peer
//
// UCP uses P2P networks to manage community & money data. Since only
// members can write to the Blockchain, it is important to have
// authenticated peers so newly validated blocks can be efficiently
// sent to them, without any ambiguity.
//
message Peer {
    // current structure version
    required uint32 version = 1;

    // currency name
    required string currency = 2;

    // public key of the node
    required bytes public_key = 3;

    message Endpoint {
	// protocol name
	optional string protocol_name = 1 [default = "BASIC_MERKLED_API"];

	// dns name
	optional string dns = 2;

	// ipv4
	optional string ipv4 = 3;

	// ipv6
	optional string ipv6 = 4;

	// port number
	optional uint32 port = 5;
    }

    // a list of endpoints to interact with the node
    repeated Endpoint endpoints = 4;

    extensions 100 to 199;
}

message Status {
    // current structure version
    required uint32 version = 1;

    // currency name
    required string currency = 2;

    enum StatusChoice {
	ASK = 0;
	NEW = 1;
	NEW_BACK = 2;
	UP = 3;
	UP_BACK = 4;
	DOWN = 5;
    }

    // status type
    required StatusChoice status = 3;

    // currrent nodes timestamp
    required uint32 time = 4;

    // public key of the issuer for this message
    required bytes issuer = 5;

    // public key of the recipient for this message
    required bytes recipient = 6;

    extensions 100 to 199;
}

For instance, you can build for Python using the following command line:

protoc --python_out=. ucoin.proto

1 Like

Waow! I will definitely have a look at this. Thanks for your .proto file, and sharing about this tool :slight_smile:

I have worked around using the Protocol Buffers Python API and coded two program that respectively write and read blocks within a blockchain (not all the structure fields are considered):

blockchain_write.py

#!/usr/bin/python2

import ucoin_pb2 as uc, sys

# This function fills in a Block message based on user input.
def PromptForBlock(block):
    block.version = int(raw_input("Enter version number: "))
    block.currency = raw_input("Enter currency name: ")
    block.nonce = int(raw_input("Enter nonce value: "))
    block.number = int(raw_input("Enter block number: "))
    block.date = int(raw_input("Enter date timestamp: "))
    block.confirmed_date = int(raw_input("Enter confirmated date timestamp: "))
    ud = raw_input("Enter universal dividend amount (blank for none): ")
    if ud != "": block.universal_dividend = int(ud)
    block.fees = int(raw_input("Enter fees amount: "))
    block.issuer = raw_input("Enter issuer public key: ")
    block.previous_hash = raw_input("Enter previous keyblock fingerprint: ")
    block.previous_issuer = raw_input("Enter previous keyblock issuer public key: ")
    block.members_count = int(raw_input("Enter the number of members in the WoT: "))

# Main procedure: Reads the entire blockchain from a file, adds one
#   block based on user input, then writes it back out to the same
#   file.
if len(sys.argv) != 2:
    print "Usage:", sys.argv[0], "BLOCKCHAIN_FILE"
    sys.exit(-1)

bc = uc.Blockchain()

# Read the existing blockchain.
try:
    f = open(sys.argv[1], "rb")
    bc.ParseFromString(f.read())
    f.close()
except IOError:
    print sys.argv[1] + ": Could not open file.  Creating a new one."

# Add a block.
PromptForBlock(bc.blocks.add())

# Write the new blockchain back to disk.
f = open(sys.argv[1], "wb")
f.write(bc.SerializeToString())
f.close()

blockchain_read.py

#!/usr/bin/python2

import ucoin_pb2 as uc, sys

# Iterates though all blocks in the Blockchain and prints info about them.
def ListBlocks(bc):
  for block in bc.blocks:
    print "block version:", block.version
    print "  Currency:", block.currency
    print "  Nonce:", block.nonce
    print "  Number:", block.number
    print "  Date:", block.date
    print "  Confirmed date:", block.confirmed_date
    if block.HasField('universal_dividend'):
      print "  Universal dividend:", block.universal_dividend
    print "  Fees:", block.fees
    print "  Issuer:", block.issuer
    print "  Previous hash:", block.previous_hash
    print "  Previous issuer:", block.previous_issuer
    print "  Members count:", block.members_count

# Main procedure: Reads the entire blockchain from a file and prints
#   all the information inside.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "BLOCKCHAIN_FILE"
  sys.exit(-1)

bc = uc.Blockchain()

# Read the existing address book.
f = open(sys.argv[1], "rb")
bc.ParseFromString(f.read())
f.close()

ListBlocks(bc)

Thanks to @canercandan for all your great work (I loved the WoT render with dot !). If you need any help (I more of a C++ guy) let me know.

ps : “Cap’n proto” looked like a great alternative to protocol buffer too (faster to parse with an RPC protocol, just to let you know).

Thanks I appreciate :wink:

I am a C++ guy too :wink: I foresee to implement soon a C++ version so we stay in touch :slight_smile:

I dont know how much I can trust a project claiming to be infinitely faster :smiley:

It sounds interesting I will test it as well. Thanks.