Released: Tuesday, April 11th 11:59pm EST
Due: Tuesday, April 25th 11:59pm EST
Now that we're familiar with a blockchain's Layer 1 from Coin, it's time that we move on to a Layer 2 scaling solution! In class, you've learned about off-chain payment channels. Lightning is a scaling protocol for Bitcoin, and we've done our best to imitate that here.
While we did cover Lightning (very) briefly in class, we strongly recommend that you watch the following 20 min video to better understand what we're asking you to do. Without a strong conceptual understanding, this project will be a toughie. And that would suck :(
For this assignment, you will implement the functions listed below. We recommend that you tackle the functions in the following order:
pkg/node.go: BroadcastTransaction
pkg/server.go: GetWitnesses
pkg/server.go: ForwardTransaction
pkg/wallet.go: GenerateFundingTransaction
pkg/wallet.go: HandleRevokedOutput
lightning/server.go: OpenChannel
lightning/server.go: GetUpdatedTransactions
lightning/server.go: GetRevocationKey
lightning/channel.go: CreateChannel
lightning/lightningnode.go: UpdateState
lightning/watchtower.go: HandleBlock
To see how difficult we think each function is, check out our grading breakdown.
The segregated witness protocol is one of the most beloved soft forks in Bitcoin. By segregating the witnesses from the transactions, miners can squeeze more transactions into each block. While they may or may not receive more in fees (long-term equilibrium anyone?), it certainly increases the throughput of the network. It's estimated that signatures take up ~65% of a block's capacity. By removing signatures from blocks, Bitcoin's scalability signifcantly improves!
This part of the assignment should be relatively quick: it's only updating a few lines of code here and there.
Stencil update:
We've added two new fields to transactions: segwit and witnesses. Segwit is a boolean flag letting a node know whether this transaction is employing the segregated witness protocol. Witnesses are a list of signatures on a transaction.
Unlike most normal hashing methods (which hash all of the fields), our transaction hashes do NOT include the slice of witnesses. Instead, they omit them, allowing signatories to see a transaction and later add their signature, WITHOUT altering the transaction's hash.
Before:
Now:
context.Context
:You do NOT need to worry about context for this assignment. Whenever you see ctx context.Context
as a method parameter for a server-side function, don't worry about it!
pro.Witnesses
struct.status.Errorf(codes.Internal, "Your error message")
: Since this is an RPC, we use the status package when handling errors.SeenTransactions
and returnSeenTransactions
with a count of 1.func (a *Address) GetWitnessesRPC(request *pro.Transaction) (*pro.Witnesses, error)
: for when we need the sender to get us the witnesses!We were introduced to the wallet in the last project, but now it has to form transactions for Lightning channels!
LockingScripts:
Bitcoin has its very own scripting language, which permits the creation of coins that have to be signed by multiple people. While we didn't create our own scripting language, we did our best to emulate Bitcoin Script with our 3 different types of locking scripts in pkg/script/lockingscript.go
.
PayToPublicKey: this is the most common type of lockingScript. If I want to send a coin to someone, they only need to prove that they are the owner of the public key to use it (validation requires their private key).
MultiParty: These are the types of scripts that we will be making use of for our Lightning channels. They contain the public keys for both members of a channel, as well as a revocation key and a number of additional blocks. No one can use the TransactionOutput
unless both parties have signed off on the Transaction
as witnesses!
HashedTimeLock: We (unfortunately) won't be using these for this assignment, but this is how we can create hops between multiple nodes and truly take advantage of the Lightning network!
fee
passed in to the function is actually worth 2 fees! Half is to cover the funding transaction, and the other half is to cover the refund transaction (which the Lightning node handles later on).amount + fee
. This is because we need to provide enough for both the funding and the refund transaction.generateTransactionOutputs
Won't work for this.amount
. The second is the coin for the counterparty, which always has an amount of 0
. If there is a third output, it's change that goes back to the channel's funder (since it's using their coins) once the channel is closed. Change needs to include the fee for the refund transaction.lockingScript
for all of the coins. We'll use a MultiParty
locking script to do so. In this case, there is no revocation key, so you can leave that field as []byte{}
. This script enforces that both parties have signed off on the funding transaction.proto.Marshal(m message) ([]byte, error)
: how to convert a pro.MultiParty
to a byte slice.generateTransactionInputs
: to create the inputs for our transactionlockingScript
. If it isn't, we can just return nilPayToPublicKey
with our public key.w.Config.DefaultFee
.RevKeySuccessful(lockingScript []byte, secRevKey []byte, scriptType int) bool
: returns whether the secret revocation key validates the lockingScript
proto.Marshal(m message) ([]byte, error)
: how to convert a pro.PayToPublicKey
to a byte slice.utils.Sign(sk *ecdsa.PrivateKey, h []byte) ([]byte, error)
: how to sign something. A transaction's hash is a string, which we can quickly convert to bytes.A Lightning node runs its own server, which is how it receives requests from other lightning nodes and responds to them. You'll be implementing the server-side responsibilities of a lightning node: handling open channel requests, updated transaction requests, and revocation key requests.
Both Alice and Bob have their own versions of each state, since the revocable outputs are different for each side. Tx(1,A) is Alice's transaction for state 1. Rev(3,B) Is Bob's revocation key for his transaction at state 3.
In the example below, Alice and Bob have an existing channel set up. Alice wants to pay Bob, so she has to be the one to initiate the protocol. If Bob wanted to pay Alice, the process would be inverted.
PeerDb
. If they aren't, we should stop this function's execution early and return.TheirTransactions
and MyTransactions
. That won't be the case in the future!MyRevocationKeys
, which we'll send over when we want to update state later.PeerDb.Get(string) *Peer
: returns nil if the address is not in our PeerDb
.func (ln *LightningNode) ValidateAndSign(tx *block.Transaction) error
: how to validate and sign a transactionGenerateRevocationKey()
: generates a public, private key pair (in byte form)TheirTransactions
and the secret key we just made to MyRevocationKeys
.func (ln *LightningNode) generateTransactionWithCorrectScripts(peer *peer.Peer, theirTx *block.Transaction, pubRevKey []byte) *block.Transaction
: creates a transaction with the correct locking scripts.MyTransactions
.RevocationInfo
that contains all of the information necessary to claim the counterparty's coin from the previous state.RevocationInfo
, we need to figure out what type of script they were using on their transaction and figure out which coin their revocation key belongs to.TheirRevocationKeys
.func DetermineScriptType(b []byte) (int, error)
: how to determine the type of a locking scriptNow we're taking on Alice's side of the protocol. An owner of a lightning node probably wants to establish a couple of channels, so that they have more opportunities to take part in multi-channel hops (not part of this assignment) and hopefully collect some routing fees along the way!
Here's the protocol from above, for easier reference.
WalletRequest
to the wallet, so that it can use valid coins to create the funding transaction for us. The fee here has to be twice the fee provided as an argument, to cover both the funding and refund transaction.MyRevocationKeys
.func (ln *LightningNode) generateFundingTransaction(request WalletRequest) *block.Transaction
: How to get a funding transaction from the wallet.func (ln *LightningNode) generateRefundTransaction(theirPubKey []byte, fundingTx *block.Transaction, fee uint32, revKey []byte) *block.Transaction
: How to create a refund transaction given a funding transaction.func (a *Address) OpenChannelRPC(request *pro.OpenChannelRequest) (*pro.OpenChannelResponse, error)
: RPC call to request opening a channel with another lightning node.MyTransactions
, sign their transaction, and add their transaction to TheirTransactions
.RevocationInfo
in TheirRevocationKeys
.func (a *Address) GetUpdatedTransactionsRPC(request *pro.TransactionWithAddress) (*pro.UpdatedTransactions, error)
: making an RPC call on the address's server to get our transaction signed and their updated transaction.func (a *Address) GetRevocationKeyRPC(request *pro.SignedTransactionWithKey) (*pro.RevocationKey, error)
: making an RPC call on the address's server to get the revocation key for their transaction.This assignment is autograded, and you are able to run our test suite as many time as you like. While you can run all of your tests on Gradescope, we have also provided the entire test suite locally. We are hoping that this change can help students get a little bit more experience using GoLand's debugger, as well as better understand the architecture of the project.
In addition, you should write your own tests when things aren't working! We have provided several helper functions in test/mocking_utils.go
and test/testing_utils.go
.
To test your project, cd
into test
and run go test
. This will let you know which tests are failing, and why. It will likely be more convenient to run individual tests, which you can do using the GoLand UI.
Note: most of our tests will cause the debugger to panic because they rely on functions that have not yet been implemented.
go get ./...
to install the project's dependencies.Note: Because of the new testing methodology we are taking with Lightning, this assignment's feedback form is a REQUIRED part of the assignment, worth 5 points. We want to understand how students prefer testing to look, as well as how different ways of testing can alleviate TA burden while maintaining educational and academic integrity.
We will also be performing some data analytics to see how big of a difference this change makes in terms of project completion time, average grade, etc…
The feedback form can be found at this link.
This assignment is out of 100 points. Your grade will be determined by completing the feedback form and passing the following test functions:
Test Function | Points |
---|---|
TestGetWitnessesRPC |
5 |
TestForwardTransaction |
5 |
TestGenerateFundingTransaction |
10 |
TestHandleRevokedTransaction |
10 |
TestOpenChannel |
10 |
TestGetUpdatedTransactions |
15 |
TestGetRevocationKey |
10 |
TestCreateChannel |
10 |
TestUpdateState |
15 |
TestWatchTowerHandleBlock |
5 |
Feedback Form Completion | 5 |
Total | 100 |