In this project you will develop BlockArt, a blockchain-based
system to support collaborative computer art projects. Each block in
the chain will add/remove visual elements on a canvas that represents
the artwork. Your blockchain network must be built to support
thousands of nodes and must use a flooding protocol to disseminate
operations on visual elements and generated blocks. Applications can
interact with your blockchain and participate in an art project
instance by using an API that you will provide. Your blockchain will
validate authenticity of operations (verify that a particular
application introduced an element) and your system must use a proof of
work algorithm to guarantee fairness (artist applications with more
computational power get to contribute more elements to the canvas).
There are several extra credit options, including adding new
shapes to the system, and developing a browser viewer that will allow
a user to view the art work and watch it evolve over time.
You will deploy and test your BlockArt system on
the Azure cloud.
There are three types of nodes in the system: some number of ink
miners, some number of art nodes, and a
single server node. There is one server per art project/canvas,
and an ink miner/art node can only participate in one canvas at a
time. The server provides a simple interface to register/retrieve ink
miners and keeps track of ink miner failures. The ink miners form a
peer-to-peer graph (based on information provided by the server). Each
miner is connected to some number of other miners (at least three in
the diagram below):
Ink miners. The ink miner is initialized with information about
the server's address and a public/private key pair. The miner should
connect to the server to register, retrieve settings, and lookup other
ink miners to connect to. The miner can only communicate with
miners that it learns about from the server. If the number of miners
it knows about dips below a threshold specified in settings,
MinNumMinerConnections, (because of failures), then the miner should
ask the server for more miners (but not otherwise). At some later
point, the miner may receive a connection from one or more art
nodes. The miner must be able to support several simultaneous art
nodes. The miner must validate that the art nodes connecting to it
know the public/private keys that the ink miner is configured with.
An ink miner has two roles: it mines ink on behalf of a specific
private/public key pair, and it also participates in the miner
network. Miners have the following responsibilities:
The ink miner must disseminate operations sent to it by art
nodes that are connected to it to the rest of the ink miner
network. The miner must also help other miners with their
A miner mines ink for the applications associated with
the art nodes to use. Without sufficient ink, the application
cannot add new elements to the canvas. Mining is done using a
proof of work algorithm based on what you developed
in assignment 1.
By solving the proof of work, the miner generates a block for
the blockchain and some amount of ink that is credited to the
public/private key pair that it was initialized with. The
generated block includes the set of operations that the miner
knows about (or is a no-op block; see below).
A miner validates mined blocks it receives and disseminates
valid blocks (that it mined locally and that other miners mined)
to the rest of the miner network.
Periodically, the miner must ping the server with a
Art node. An art node provides an interface to a local
application (art app) through a Go library called blockartlib
to add/remove elements on the canvas:
The art app will initialize blockartlib with information about the
miner's address and a public/private key pair. The art node will
connect to miner and validate that this miner mines on behalf of the
right key pair. The art node can only communicate with the one
ink miner that was specified on the command line. The application will
then use the local blockartlib instance to perform operations on the
global canvas and to retrieve information about the blockchain. Art
nodes do not communicate between one another.
Server. The server helps miners find each other. We will give
you a simple version of the server. You can change the server code in
your development, but you cannot change the interface between the
server and the miners.
APIs. The BlockArt system has several APIs. It is important to
understand which ones are your responsibility to design and
implement. Here is a summary:
art app — blockartlib : API specified below; you will have
to implement this API.
ink miner — server: API specified below, the server side
of the API is implemented and given to you.
art node — ink miner: not specified, and up to you to
design and implement.
ink miner — ink miner: not specified, and up to you to
design and implement.
You have substantial flexibility in the design and implementation of
this project. Keep things simple and design as much of the system as
you can before starting on the implementation.
This library exposes an interface that is detailed in
the blockartlib.go file. You
are not allowed to change this interface. The library
generally has two kinds of calls: calls to manipulate the canvas, and
calls to retrieve information about the blockchain. All of the calls
require connectivity and return DisconnectedError if the art
node is no longer connected to the network. All of the calls are
blocking calls: they do not return until they have finished
successfully, or an error occurred.
Opens a new canvas, providing the miner address that is mining on
behalf of the public and private key pair arguments. Returns
error, or if error is not set, then a canvas reference/instance
and settings for this canvas instance. Error can
inkRemaining, err ← canvas.CloseCanvas()
Closes the canvas/connection to the BlockArt network. Returns
error, or if error is not set, then the amount of remaining ink
owned by this application instance. Error can
Adds a new shape of type shapeType and description string
shapeSvgString to the canvas. validateNum specifies the
number of blocks (no-op or op) that must follow the block with
this operation in the block-chain along the longest path before
the operation can return successfully. Returns error, or if error
is not set, then returns the hash of the shape, the blockHash of
the block to which this shape was added, and ink remaining. Error
can be DisconnectedError, InsufficientInkError,
Your library must provide support for the PATHshapeType. As extra credit you may add other shape
varieties. The path shape is modeled after the
path. You only have to support the following path
commands: M, m (move to), L, l (line
to), H, h (horizontal line), V, v (vertical
line), Z, z (draw straight line back to starting
position). Capital letters denote commands relative to absolute
canvas coordinates, while the lower-case letters denote commands
against relative coordinates. See
for more information.
The shapeSvgString captures the command sequence. This
string can be at most 128 characters long. For the PATH
shape type, this string must be a legal svg d attribute
to the svg path element and can include only the commands
listed above. If this string is too long or invalid in format,
then a ShapeSvgStringTooLongError
or InvalidShapeSvgStringError should be returned,
The fill argument is a string that determines whether the
shape has no fill: transparent, or the shape has fill of
a certain color, e.g., red. A shape with transparent fill
uses no ink for the area inside the shape; a shape with
non-transparent fill also uses ink for the filled area. For
example, the PATH command "M 0 10 H 20" would use 20 units
But, the command "M 0 0 H 20 V 20 h -20 Z" with non-transparent
(blue) fill and non-transparent (red) stroke would use 20*20
(area) + 20*4 (length) = 480 units of ink:
Here are the precise calculations you must implement:
transparent fill + non-transparent stroke ink used = length
non-transparent fill + transparent stroke ink used = area
non-transparent fill + non-transparent stroke ink used = area+length
(transparent fill + transparent stroke: InvalidShapeSvgStringError)
An InsufficientInkError should be returned if the shape
requires more ink than what the application instance has currently
(i.e., ink mined by the miner, minus ink used to create shapes by
applications with the corresponding key pair in the past). Ink
mined to generate the block that will contain this shape operation
does not count.
OutOfBoundsError is returned if the shape exceeds the
bounds of the canvas. ShapeOverlapError error is returned
if this shape intersects/overlaps (in at least 1 point) with a
shape that was added previously by another application, but not
You do not need to support arbitrary shapeSvgString
strings (though you can, if you want to). Here are some
constraints to make the parsing easier. Expect
that shapeSvgString abides by the following:
Uses only spaces for delimiters (no commas)
Always has a space between the command name and the arguments (e.g., "M 1 2", not "M1 2")
Each command in the string has a fixed set of arguments (e.g., no "M 20 20 l 50 20 15 25", 2 extra args to l)
Always starts with the "M" command (necessary since the set of operations in the block is unordered)
There is one "M" command (at the start of the string) if fill is non-transparent
Stroke and fill are required arguments. Stroke and fill args
cannot both be empty strings and cannot both be set to
"transparent". In these cases return
svgString, err ← canvas.GetSvgString(shapeHash)
Returns error, or if error is not set, then returns the encoding
of the shape identified by shapeHash as an svg string. For example
a PATH shape with command "M 0 0 L 20 20" and stroke "red" and
fill "transparent" would return the following string: <path d="M 0 0 L 20 20" stroke="red"
Which can be rendered in a browser
(on a 20x20 canvas) to look like:
Can return DisconnectedError and InvalidShapeHashError.
An InvalidShapeHashError should be returned if the shape
with shapeHash cannot be found.
inkRemaining, err ← canvas.GetInk()
Returns the amount of ink currently available to the
application. Can return DisconnectedError.
Removes a shape with shapeHash from the
canvas. validateNum specifies the number of blocks (no-op
or op) that must follow the block with this operation in the
block-chain along the longest path before the operation can return
successfully. Returns the amount of ink currently available to the
application. Deleting a shape credits the application with the ink
that the shape was using (e.g., if a PATH shape "M 0 10 H 20" is
deleted, then the application's ink supply would go up by 20
return DisconnectedError and ShapeOwnerError errors.
ShapeOwnerError is returned if this application did not
create the shape with shapeHash (or if no shape exists with shapeHash).
shapeHashes, err ← canvas.GetShapes(blockHash)
Returns the set of all shape hashes that belong to a block
identified by the hash blockHash. Can
return DisconnectedError and
InvalidBlockHashError if no block with blockHash exists.
blockHash, err ← canvas.GetGenesisBlock()
Returns the block hash of the genesis block in the blockchain.
Can return DisconnectedError.
blockHashes, err ← canvas.GetChildren(blockHash)
Returns the set of all children blocks for block identified by the
hash blockHash. Can return DisconnectedError and
InvalidBlockHashError if no block with blockHash exists.
Consult the sample art application that uses
this interface for an example of how this interface can be used.
Notes on the BlockArtLib API.
If the fill is set to transparent, then the application should be
able to construct an arbitrary path using the allowed commands. For
example, the shape might self-intersect at one or more points.
If the fill is not transparent, then the application is
constrained to constructing one simple closed curve (no
self-intersections, no end points, and completely encloses an
area), such that the first and last points along the path are the
same. This last constraint could be satisfied by using
a Z command, or by explicitly specifying identical x,y
coordinates (in the absolute or relative coordinate systems).
In the API inkRemaining is a uint32. This means that
you have to do some rounding in case of fractional ink. You should
always round up the value returned
Shapes generated by different art applications cannot
overlap/intersect. However, shapes added by the same
application can intersect. Note that the overlap region
does not save ink for the application (e.g., repainting the same
line segment uses up ink).
The shape spec is a translation of the svg spec. When in doubt
about what the shaded area looks like, or if a certain command
sequence is legal, check with the svg spec, run an experiment in a
browser, or find/use a tool.
The server listens to connections from ink miners on a known TCP
Server — ink miner API. An ink miner can invoke three
kinds of TCP RPCs against the server:
settings, err ← Register(address, publicKey)
Registers a new miner with an address for other miner to use to
connect to it (returned in GetNodes call below), and a
public-key for this miner. Returns error, or if error is not set,
then setting for this canvas instance. Returns
AddressAlreadyRegisteredError if the server has already
address. Returns KeyAlreadyRegisteredError if the server
already has a registration record for publicKey.
addrSet,err ← GetNodes(publicKey)
Returns addresses for a subset of miners in the system. Returns
UnknownKeyError if the server does not know a miner with
err ← HeartBeat(publicKey)
The server also listens for heartbeats from known miners. A miner must
send a heartbeat to the server every HeartBeat milliseconds
(specified in settings from server) after calling Register, otherwise
the server will stop returning this miner's address/key to other
miners. Returns UnknownKeyError if the server does not know a
miner with this publicKey.
The ink miner should follow the following state machine in
calling GetNodes ("min" in the diagram stands
Each miner must implement a mining procedure by which it can
generate ink and add a new block in the block chain. A node can
only compute one block at a time and cannot work on multiple
blocks simultaneously. There are two kinds of blocks: no-op
blocks and op blocks.
A no-op block does not contain any operations. These
blocks are generated so as to prevent pre-computation of long
block sequences by malicious nodes and to validate existing
operations (the validateNum argument to AddShape and DeleteShape
controls validation from the art application's side). All nodes
should always be working on no-op blocks in the background,
constantly generating these and adding them to the
An op block contains several operations. When a client
notifies blockartlib of an operation, the local art node sends the
operation to the miner. The miner immediately stops working on
no-op blocks (but not op blocks) and switches to work on an op
block to integrate the new operation into the block-chain. The
miner can and should integrate multiple operations that it knows
about (potentially broadcast/generated by other miners) into one
Block chain. Miners maintain a tree representation of the
block chain. The chain is the longest path in this tree,
starting at the genesis block whose hash is specified by the
server. A miner should only compute no-op and op blocks along the
chain, and not along any shorter path in the tree. In the case that
there are several (longest) chains, the miner should (1) pick the one
that does not cause a validation error for the current op block it is
generating, or if no op block is being generated or none cause a
validation error, then (2) the miner should pick among the chains
uniformly at random.
Block data structure. An op block is a data structure that
contains at least the following data:
A hash of the previous block in the chain (prev-hash)
An unordered set of operation records; each operation record should include:
An application shape operation (op)
A signature of the operation (op-sig)
A public key of the art node that generated the op (used to validate op/op-sig)
The public key of the miner that computed this block (pub-keyMiner)
A 32-bit unsigned integer nonce (nonce)
Block hashes (e.g., prev-hash) must be an MD5 hash and must contain a
specific number of zeroes at the end of the hash (a constant
specified during registration with the server). A op block's hash is a
hash of [prev-hash, op, op-signature, pub-key, nonce]. The goal of the
proof-of-work algorithm is to find a nonce such that the block's hash
contains the required number of zeroes. The more zeroes, the longer it
takes to generate the block (find a nonce that works).
Note that the shapeHash argument in the blockartlib API can
be the signature of the shape (op-sig). However, it does not have to
be the signature; it can be any string that uniquely
and consistently identifies the shape globally in the
blockchain. Consistently here means across different art apps and
different API call instances.
Note that a block hash computed for an op block or a no-op block by a
miner A will always be different from blocks generated by other miners
(for the same op/no-op) and will always differ from other blocks
generated by miner A. This is because a block contains a prev-hash,
which uniquely identifies its position in the blockchain tree, and a
block contains a public key (e.g., known by node A), which makes each
block unique to A.
A no-op block is identical to an op block except that it does not
include an op. Its hash is similarly computed using a proof-of-work
Block validation. This project does not assume trust between
miners. For example, miners may attempt to cheat (spend ink they don't
have) and steal (spend ink belonging to other nodes). That is, you
should design your miner with the assumption that other miners may be
malicious. A key aspect of your design must be a set of routines to
validate and check each piece of information that you receive from
other miners. It is especially important to validate blocks, since
un-validated blocks can completely undermine your system.
Here is a minimal set of validations each miner should perform:
Check that the nonce for the block is valid: PoW is correct and
has the right difficulty.
Check that each operation in the block has a valid signature
(this signature should be generated using the private key and the
Check that the previous block hash points to a legal, previously
Check that each operation has sufficient ink associated with the
public key that generated the operation.
Check that each operation does not violate the shape
intersection policy described above.
Check that the operation with an identical signature has not
been previously added to the longest chain in the blockchain. This
prevents operation replay attacks.
Check that an operation that deletes a shape refers to a shape
that exists and which has not been previously deleted.
BlockArtLib API. Each art node must implement the blockart lib
interface. For this, the node must (1) store all operations that have
acted on the canvas thus far, and (2) it must respond to drawing
operations against this version of the canvas. The library must
continue to respond to application operations even though the
block-chain underneath changes.
Two concurrent and conflicting shapes (i.e., intersecting shapes
added by two applications with different keys) cannot be added to
the same block. Your design must guarantee this.
Conflicting shapes also cannot appear along the longest path in
the blockchain (this check must be part of validating an
operation). This means that if two applications with different
keys simultaneously issue operations for shapes that intersect,
then one of the applications should (eventually) get back
a ShapeOverlapError. The other operation must succeed. In
general, exactly one of the conflicting operations
must succeed, while the others must get
Ink miners and art nodes may fail stop. If an art node fails
mid-operation, the miner may abort or it may commit the art node's
operation. If the ink miner that an art node is connected to fails,
the blockartlib at the art node should return
a DisconnectedError to the art app.
Ink miners and art nodes should not store/cache any state on disk. A
failure therefore completely wipes out the node's state.
As long as an ink miner is connected to at least one other ink miner,
it should continue to operate in the miner network. The ink miner
should only call the server's GetNodes to retrieve more
mining nodes if the number of non-failed miners that it is connected
to drops below MinNumMinerConnections (returned as part of
settings from the server).
The Azure cloud is composed of several data-centers,
world-wide. Each data center hosts many thousands of machines that
can be used (i.e., rented) for a price. In this project you may use the Azure cloud to deploy and test your solution in the wide area.
Although you will test and deploy your system on Azure, it will have
nothing Azure-specific about it, e.g., it should be possible to run
your system on the CS ugrad servers without any code modifications.
Using Azure: stop VMs when not using
slides presentation covering the basic workflow of getting a VM
running on Azure for this/future assignments. To setup the Go
environment in a VM you can use
the azureinstall.sh script.
(1) Write a go program called ink-miner.go, (2) a Go library
called blockartlib.go that behave according to the
description above, and (3) an application that uses blockartlib and
produces an html file as output that contains an svg canvas that is
the result of the application distributed activity (the
application must be runnable at different ink miner node instances
Ink miner's command line usage:
go run ink-miner.go [server ip:port] [pubKey] [privKey]
[server ip:port] : the IP:port address of the server in the
[pubKey] [privKey]: the public and private key pair
(represented as two string command line arguments) that this
miner should use to validate connecting art nodes and towards
which it should credit mined ink.
You can use a stub implementation of the blockartlib API and a client
application that uses it as starters for your system:
Note that this is a draft that will remain a draft until next week
Monday evening after we have attempted this rubric with the first set
of teams. It captures my ideal version of what I want in the
demo. But, teams might run out of time, it might be impractical to
modify code live, many VMs might prove too complex, etc. So, this
rubric/demo sequence might get simpler based on our experience with
demos on Monday, Feb 19th.
At the high level your mark for project 1 is 20% of your final mark
and has these components:
Peer review multiplier
Note that the demo actually exercises both the Code and the Demo
portion of your mark. So, the best way of thinking about the demo
rubric below is that it is 100% of your mark for project 1. Of course,
we reserve the right to look at your code on our own if we want to to
further convince ourselves about some functional aspect or check that
you do indeed implement some particular piece (e.g., flooding of
The peer review multiplier will be an online survey. In the survey you
will be asked to partition some number, like 100, into as many parts
as there are team members in your project 1 team. The survey results
will only be available to instructors (not revealed to your team
members). The rough formula I will use is that the average of
fractions that your team members assign you relative to an equal split
will become a multiplier for your demo mark. In some cases, if there
are substantial discrepancies (e.g., teammates give each other 0s), I
might interview team members to get to the bottom of the story and
figure out the multiplier accordingly.
The demo has four parts, along with percentage of your project mark:
Part A (40%) : you demo for us your system using your own code (your own server/miner/art apps). This is also your chance to show us any extra credit options that you have implemented.
Part B (30%) : you demo for us your system with our server (+ your miners/art apps)
Part C (10%) : you demo for us your system with our server and changes to some of your art apps (+ your miners)
Part D (30%) : we ask you design-level and code-level questions about your system
Except for the first 4 demos, each demo will have 2 instructors --
either two TAs or Ivan and one of the TAs. One of us will be taking
notes, and the second person will be laser focused on your demo --
what you are doing, how you are doing it, clarifying what's going on,
Each team has a guaranteed slot of 20 minutes. Each part of the demo
is expected to take about 5 minutes. Note that 20 minutes may be a
hard cut off, especially in cases where there is another team
scheduled after your team. (We give ourselves about 5-10 minutes to
deliberate your mark and review the demo after you leave the room. The
more time we have for this, the more likely that you will get a fair
Note that your demo will be run in our environment. That is, when you enter the demo room, we will have a set of Azure VMs up and running and ready for your demo. Each VM will have:
Go version 1.9.2 installed
Your code cloned/checked out from stash (last version before deadline)
A file at top level of your repo called server.ip containing one line with the IP of the VM where the server should run
A file at top level of your repo called miners.ip containing as many lines as there are VMs available, with each line containing an IP of a VM where a miner should run
Your demo in part (A) must obey the following parameters:
The server must run on a separate VM (specified in server.ip)
Each miner must run on a separate VM (specified in miners.ip)
Art-apps must be co-located with the miner that they are connected with (run on same VM as the miner that they are connected to)
There must at least 1 miner with more than 2 connected art apps
There must be exactly 1 miner without any art apps (others have at least 1 art app)
Your miners network must include 5-10 miners
You must use PoW difficulty less than or equal to 4 (for both op and no-op blocks)
Your part A must run and output the HTML file that we you can download/preview in under 5 minutes. We will try not to cut you off, but if you go over you will have less time for the the other parts of the demo. You must track your own time and be fast.
You kill/stop all of the art apps and miners once you are done with Part A
Part B (with our server):
We reveal a server that is conveniently running on Azure as a VM at a certain IP:port.
This server respects the server API but it constructs a miner topology that is unknown to you (a focus of this part of the demo is to see if your code works with an arbitrary topology).
You start all of the miners to connect to this new server
You start the art apps and run through your demo as in Part A
You kill/stop all of the art apps and miners once you are done with Part A
We consider the HTML file output (it should be identical modulo race conditions; which you can explain to us as part of the demo).
Part C (with our server + changes to some of the art apps):
We give us the file names for two art apps that are connected to two different miners in your network
We hack the art apps -- delete code, and add some code. For example,
We might call AddShape on a square that is HUGE (should fail with insufficient ink error)
We might call AddShape on two lines that intersect (should fail with overlap error)
We might add a loop that counts number of blocks in the chain, outputs that number, sleeps, and re-loops.
You start all of the miners
You start all of the art apps
We observe the modified art apps to see if they behave in the expected manner
Part D (design Q/A):
We will ask each person on the team one question from a list of questions that we have curated for this part. How many people we ask will depend on time.
We will try to make sure there is a whiteboard available for you to draw diagrams if you need one.
Each person on the team should attempt to answer the question on their own first. If the person is unable to do this, then the rest of the team (whoever else wants to help) can jump in and help answer the question.
The questions are high-level design questions about your system. They are the kinds of questions that everyone on the team should know the answer (at least to some degree!). Some examples:
What is your protocol for distributed operations in the miner network?
What is your approach for determining shape intersections?
How does your system figure out ink remaining to return to the art app after it calls AddShape?
What happens if the miner fails before it is able to disseminate an operation to other miners?
How does miner A check the signature of an operation generated by art app connected to miner B?
These questions may escalate into discussion about your design choices more broadly. (So, we may not get to ask everyone on the team a question.)
Nice to have, or strongly recommended for the demo:
Strongly recommend to pre-generate a set of ECDSA keys for your
demo use (pre-generate a bunch, like 20). Commit these keys into your
repo (note: this is bad practice in general; but ok for course project
demos). You can commit them in whatever format you want -- as 1 file
with a bunch of keys, or 1 file per key, whatever you think would best
facilitate their usage during the demo.
Strongly recommend including (comprehensible) stdout/logging in
your codebase so that we can track things as they occur on the
terminal. If you lack this, then we can only go by the output html
string, and if that doesn't work, then it will be hard (impossible)
for us to justify any partial marks.
What to bring for the demo:
You'll need your own laptop to drive the demo. I recommend bringing several so that others can monitor the system, and you have some redundancy + 8 hands can type faster than 2 hands.
This project is extensible with three extra credits:
EC1 [1% of final mark]: Add the circle shape to blockartlib,
modeling it after
circle element. Add the appropriate ShapeType, encode the
required circle attributes in shapeSvgString, and include
support for fill and stroke attributes. Get ready to do some math to
figure out pair-wise shape intersections, circle perimeters/areas,
canvas out of bounds errors, etc.
EC2 [1% of final mark]: Create an art application that includes a
built-in web server that serves a page that can be accessed in a
browser. The page should display a navigable and visual history of
the block-by-block evolution of the svg canvas in the BlockArt
network. The browser-side JS can use whatever libraries you
EC3 [0.5% of final mark]: Similar to EC2, but allow the user to
add shapes to the canvas in real-time and observe the resulting svg
canvas (as stored within the blockchain)