From 8c6eb265a63d1ddaa2d82aeaf5c1c722c6802f30 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 9 Aug 2017 20:56:00 -0700 Subject: [PATCH 1/2] added transaction creation at console; removed hotwallet --- Makefile | 2 +- app/console.go | 58 ++++++++++++++++++++-------------- app/user.go | 54 +++++++++++++++++++++++-------- blockchain/genesis.go | 2 +- blockchain/test_utils.go | 20 ++++++------ blockchain/transaction.go | 4 +-- blockchain/transaction_test.go | 14 ++++---- blockchain/wallet.go | 58 +++++++++++++++++++++++++++------- consensus/consensus.go | 2 +- consensus/consensus_test.go | 6 ++-- miner/miner.go | 2 +- miner/miner_test.go | 2 +- 12 files changed, 147 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 9c5ce5b..fee43d1 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ run-console: cumulus deps: glide install -clean: +clean: cumulus rm cumulus install-glide: diff --git a/app/console.go b/app/console.go index 5865e61..c463add 100644 --- a/app/console.go +++ b/app/console.go @@ -65,22 +65,9 @@ func create(ctx *ishell.Context, app *App) { "Transaction", }, "What would you like to create?") if choice == 0 { - createHotWallet(ctx, app) + createWallet(ctx, app) } else { - shell.Print("Sender wallet ID: ") - senderID := shell.ReadLine() - shell.Print("Recipient wallet ID: ") - recipientID := shell.ReadLine() - shell.Print("Amount to send: ") - amount, err := strconv.ParseFloat(shell.ReadLine(), 64) - if err != nil { - shell.Println("Invalid number format. Please enter an amount in decimal format.") - return - } - - // TODO: make transaction, add it to the pool, broadcast it - ctx.Printf(`\nNew Transaction: \nSenderID: %s \nRecipiendID: %s\nAmount: %f"`, - senderID, recipientID, amount) + createTransaction(ctx, app) } } @@ -119,13 +106,36 @@ func connect(ctx *ishell.Context, a *App) { } } -func createHotWallet(ctx *ishell.Context, app *App) { - shell.Print("Enter wallet name: ") - walletName := shell.ReadLine() - wallet := HotWallet{walletName, blockchain.NewWallet()} - app.CurrentUser.HotWallet = wallet - emoji.Println(":credit_card: New hot wallet created!") - emoji.Println(":raising_hand: Name: " + wallet.Name) - emoji.Println(":mailbox: Address: " + wallet.Wallet.Public().Repr()) - emoji.Println(":fist: Emoji Address: " + wallet.Wallet.Public().Emoji()) +func createWallet(ctx *ishell.Context, app *App) { + wallet := blockchain.NewWallet() + app.CurrentUser.Wallet = wallet + emoji.Println(":credit_card: New wallet created!") + emoji.Print(":mailbox:") + ctx.Println(" Address: " + wallet.Public().Repr()) + emoji.Println(":fist: Emoji Address: " + wallet.Public().Emoji()) + ctx.Println("") +} + +func createTransaction(ctx *ishell.Context, app *App) { + emoji.Print(":credit_card:") + ctx.Println(" Enter recipient wallet address") + toAddress := shell.ReadLine() + // TODO: Error handling address input. + + emoji.Print(":dollar:") + ctx.Println(" Enter amount to send: ") + amount, err := strconv.ParseUint(shell.ReadLine(), 10, 64) + if err != nil { + emoji.Println(":disappointed: Invalid number format. Please enter an amount in decimal format.") + return + } + + // TODO: Check if we have enough coins to make the purchase. + err = app.Pay(toAddress, amount) + if err != nil { + emoji.Println(":disappointed: Transaction failed!") + ctx.Println(err.Error) + } else { + emoji.Println(":mailbox_with_mail: Its in the mail!") + } } diff --git a/app/user.go b/app/user.go index 8f9a490..d507bf1 100644 --- a/app/user.go +++ b/app/user.go @@ -1,33 +1,61 @@ package app -import "github.com/ubclaunchpad/cumulus/blockchain" +import ( + "github.com/ubclaunchpad/cumulus/blockchain" + + crand "crypto/rand" +) // User holds basic user information. type User struct { - HotWallet + *blockchain.Wallet BlockSize uint32 } -// HotWallet is a representation of the users wallet. -type HotWallet struct { - Name string - blockchain.Wallet -} - // NewUser creates a new user func NewUser() *User { return &User{ - HotWallet: HotWallet{ - Wallet: blockchain.NewWallet(), - Name: "default", - }, + Wallet: blockchain.NewWallet(), BlockSize: blockchain.DefaultBlockSize, } } -// getCurrentUser gets the current user. +// getCurrentUser gets the current user function only used for app initalization. func getCurrentUser() *User { // TODO: Check for local user information on disk, // If doesnt exist, create new user. return NewUser() } + +// Pay pays an amount of coin to an address `to`. +func (a *App) Pay(to string, amount uint64) error { + // Three atomic steps must occur. + // 1. The amount must be debited from the wallet. + err := a.CurrentUser.Wallet.Debit(amount) + if err == nil { + return err + } + + // 2. A legitimate transaction must be built. + tbody := blockchain.TxBody{ + Sender: getCurrentUser().Wallet.Public(), + Outputs: []blockchain.TxOutput{ + blockchain.TxOutput{ + Recipient: to, + Amount: amount, + }, + }, + } + + // 3. The transaction must be signed and added to the pool. + if txn, err := tbody.Sign(*a.CurrentUser.Wallet, crand.Reader); err != nil { + a.HandleTransaction(txn) + } else { + // Signature failed, credit the account, return an error. + // Crediting cannot fail because debiting succeeded and process is single threaded. + a.CurrentUser.Wallet.Credit(amount) + return err + } + + return nil +} diff --git a/blockchain/genesis.go b/blockchain/genesis.go index 0e45413..2cc99a8 100644 --- a/blockchain/genesis.go +++ b/blockchain/genesis.go @@ -13,7 +13,7 @@ func Genesis(miner Address, target Hash, blockReward uint64, extraData []byte) * cbReward := TxOutput{ Amount: blockReward, - Recipient: miner, + Recipient: miner.Repr(), } cbTx := &Transaction{ diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index 34d7c49..738625c 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -30,7 +30,7 @@ func NewTestTxHashPointer() TxHashPointer { func NewTestTxOutput() TxOutput { return TxOutput{ Amount: uint64(mrand.Int63()), - Recipient: NewWallet().Public(), + Recipient: NewWallet().Public().Repr(), } } @@ -53,7 +53,7 @@ func NewTestTxBody() TxBody { func NewTestTransaction() *Transaction { sender := NewWallet() tbody := NewTestTxBody() - t, _ := tbody.Sign(sender, crand.Reader) + t, _ := tbody.Sign(*sender, crand.Reader) return t } @@ -124,7 +124,7 @@ func NewTestOutputBlock(t []*Transaction, input *Block) *Block { } // NewTestTransactionValue creates a new transaction with specific value a. -func NewTestTransactionValue(s, r Wallet, a uint64, i uint32) (*Transaction, error) { +func NewTestTransactionValue(s, r *Wallet, a uint64, i uint32) (*Transaction, error) { tbody := TxBody{ Sender: s.Public(), Input: TxHashPointer{ @@ -136,9 +136,9 @@ func NewTestTransactionValue(s, r Wallet, a uint64, i uint32) (*Transaction, err } tbody.Outputs[0] = TxOutput{ Amount: a, - Recipient: r.Public(), + Recipient: r.Public().Repr(), } - return tbody.Sign(s, crand.Reader) + return tbody.Sign(*s, crand.Reader) } // NewValidBlockChainFixture creates a valid blockchain of two blocks. @@ -150,13 +150,13 @@ func NewValidBlockChainFixture() (*BlockChain, Wallet) { trA, _ := NewTestTransactionValue(original, sender, 2, 1) trA.Outputs = append(trA.Outputs, TxOutput{ Amount: 2, - Recipient: sender.Public(), + Recipient: sender.Public().Repr(), }) trB, _ := NewTestTransactionValue(sender, recipient, 4, 1) trB.Input.Hash = HashSum(trA) - trB, _ = trB.TxBody.Sign(sender, crand.Reader) + trB, _ = trB.TxBody.Sign(*sender, crand.Reader) cbA, _ := NewValidCloudBaseTestTransaction() cbB, _ := NewValidCloudBaseTestTransaction() @@ -169,7 +169,7 @@ func NewValidBlockChainFixture() (*BlockChain, Wallet) { return &BlockChain{ Blocks: []*Block{inputBlock, outputBlock}, Head: NewTestHash(), - }, recipient + }, *recipient } // NewValidTestChainAndBlock creates a valid BlockChain and a Block that is valid @@ -192,7 +192,7 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { } tbody.Outputs[0] = TxOutput{ Amount: a, - Recipient: NewWallet().Public(), + Recipient: NewWallet().Public().Repr(), } tr, _ := tbody.Sign(s, crand.Reader) @@ -223,7 +223,7 @@ func NewValidCloudBaseTestTransaction() (*Transaction, Address) { } cbReward := TxOutput{ Amount: 25, - Recipient: w.Public(), + Recipient: w.Public().Repr(), } cbTxBody := TxBody{ Sender: NilAddr, diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 6b5e464..abc5835 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -26,14 +26,14 @@ func (thp TxHashPointer) Marshal() []byte { // TxOutput defines an output to a transaction type TxOutput struct { Amount uint64 - Recipient Address + Recipient string } // Marshal converts a TxOutput to a byte slice func (to TxOutput) Marshal() []byte { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, to.Amount) - buf = append(buf, to.Recipient.Marshal()...) + buf = append(buf, []byte(to.Recipient)...) return buf } diff --git a/blockchain/transaction_test.go b/blockchain/transaction_test.go index 72db01b..fee5f0e 100644 --- a/blockchain/transaction_test.go +++ b/blockchain/transaction_test.go @@ -2,29 +2,27 @@ package blockchain import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestTxBodyLen(t *testing.T) { txBody := NewTestTxBody() senderLen := AddrLen inputLen := 2*(32/8) + HashLen - outputLen := len(txBody.Outputs) * (64/8 + AddrLen) + outputLen := len(txBody.Outputs) * (64/8 + ReprLen) txBodyLen := senderLen + inputLen + outputLen - if txBody.Len() != txBodyLen { - t.Fail() - } + assert.Equal(t, txBody.Len(), txBodyLen) } func TestTransactionLen(t *testing.T) { tx := NewTestTransaction() senderLen := AddrLen inputLen := 2*(32/8) + HashLen - outputLen := len(tx.TxBody.Outputs) * (64/8 + AddrLen) + outputLen := len(tx.TxBody.Outputs) * (64/8 + ReprLen) txBodyLen := senderLen + inputLen + outputLen txLen := txBodyLen + SigLen - if tx.Len() != txLen { - t.Fail() - } + assert.Equal(t, tx.Len(), txLen) } diff --git a/blockchain/wallet.go b/blockchain/wallet.go index 2a0dfae..e8c0485 100644 --- a/blockchain/wallet.go +++ b/blockchain/wallet.go @@ -6,7 +6,9 @@ import ( crand "crypto/rand" "crypto/sha256" "encoding/hex" + "errors" "io" + "math" "math/big" c "github.com/ubclaunchpad/cumulus/common/constants" @@ -18,6 +20,8 @@ const ( CoordLen = 32 // AddrLen is the length in bytes of addresses. AddrLen = 2 * CoordLen + // ReprLen is the length in bytes of an address checksum. + ReprLen = 40 // SigLen is the length in bytes of signatures. SigLen = AddrLen // AddressVersion is the version of the address shortening protocol. @@ -93,27 +97,32 @@ func (a Address) Key() *ecdsa.PublicKey { } } -// Wallet represents a wallet that we have the ability to sign for. -type Wallet interface { +// Account represents a wallet that we have the ability to sign for. +type Account interface { Public() Address Sign(digest Hash, random io.Reader) (Signature, error) + Debit(amount uint64) error + Credit(amount uint64) error } -// Internal representation of a wallet. -type wallet ecdsa.PrivateKey +// Wallet is an account that can sign and hold a balance. +type Wallet struct { + *ecdsa.PrivateKey + Balance uint64 +} // Key retreives the underlying private key from a wallet. -func (w *wallet) key() *ecdsa.PrivateKey { - return (*ecdsa.PrivateKey)(w) +func (w *Wallet) key() *ecdsa.PrivateKey { + return w.PrivateKey } // Public returns the public key as byte array, or address, of the wallet. -func (w *wallet) Public() Address { - return Address{X: w.PublicKey.X, Y: w.PublicKey.Y} +func (w *Wallet) Public() Address { + return Address{X: w.PrivateKey.PublicKey.X, Y: w.PrivateKey.PublicKey.Y} } // Sign returns a signature of the digest. -func (w *wallet) Sign(digest Hash, random io.Reader) (Signature, error) { +func (w *Wallet) Sign(digest Hash, random io.Reader) (Signature, error) { r, s, err := ecdsa.Sign(random, w.key(), digest.Marshal()) return Signature{R: r, S: s}, err } @@ -147,9 +156,34 @@ func (s *Signature) Marshal() []byte { return buf } -// NewWallet produces a new Wallet that can sign transactionsand has a +// NewWallet produces a new Wallet that can sign transactions and has a // public Address. -func NewWallet() Wallet { +func NewWallet() *Wallet { priv, _ := ecdsa.GenerateKey(curve, crand.Reader) - return (*wallet)(priv) + return &Wallet{ + PrivateKey: priv, + Balance: 0, + } +} + +// Debit attempts to spend some coin from the wallet. +func (w *Wallet) Debit(amount uint64) error { + if amount >= w.Balance { + return errors.New("wallet does not have a high enough balance to satisfy the request") + } else if amount < 0 { + return errors.New("debit amounts may not be negative") + } + w.Balance -= amount + return nil +} + +// Credit attempts to add count to the wallet. +func (w *Wallet) Credit(amount uint64) error { + if amount >= math.MaxUint64 { + return errors.New("wallet cannot hold this amount") + } else if amount < 0 { + return errors.New("credit amounts may not be negative") + } + w.Balance += amount + return nil } diff --git a/consensus/consensus.go b/consensus/consensus.go index d77415d..70fec24 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -82,7 +82,7 @@ func VerifyCloudBase(bc *blockchain.BlockChain, reward := StartingBlockReward / uint64(math.Pow(float64(2), timesHalved)) // Check that the output is properly set. - if len(t.Outputs) != 1 || t.Outputs[0].Recipient == blockchain.NilAddr { + if len(t.Outputs) != 1 || t.Outputs[0].Recipient == blockchain.NilAddr.Repr() { return false, BadCloudBaseOutput } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 3fef4fa..dd6de63 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -68,7 +68,7 @@ func TestVerifyTransactionSignatureFail(t *testing.T) { tr := bc.Blocks[1].Transactions[1] fakeSender := blockchain.NewWallet() - tr, _ = tr.TxBody.Sign(fakeSender, crand.Reader) + tr, _ = tr.TxBody.Sign(*fakeSender, crand.Reader) bc.Blocks[1].Transactions[1] = tr valid, code := VerifyTransaction(bc, tr) @@ -405,7 +405,7 @@ func TestVerifyCloudBaseBadOutput(t *testing.T) { b.GetCloudBaseTransaction().Outputs, blockchain.TxOutput{ Amount: 25, - Recipient: w.Public(), + Recipient: w.Public().Repr(), }, ) valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) @@ -432,7 +432,7 @@ func TestVerifyCloudBaseBadOutput(t *testing.T) { bc, _ = blockchain.NewValidBlockChainFixture() b = bc.Blocks[0] - b.GetCloudBaseTransaction().Outputs[0].Recipient = blockchain.NilAddr + b.GetCloudBaseTransaction().Outputs[0].Recipient = blockchain.NilAddr.Repr() valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) if valid { diff --git a/miner/miner.go b/miner/miner.go index a32cbc8..7d94401 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -104,7 +104,7 @@ func CloudBase( // TODO: Add transaction fees cbReward := blockchain.TxOutput{ Amount: consensus.CurrentBlockReward(bc), - Recipient: cb, + Recipient: cb.Repr(), } cbTxBody := blockchain.TxBody{ Sender: blockchain.NilAddr, diff --git a/miner/miner_test.go b/miner/miner_test.go index 2ec6a30..a9ec247 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -75,7 +75,7 @@ func TestCloudBase(t *testing.T) { t.Fail() } - if b.Transactions[0].Outputs[0].Recipient != w.Public() { + if b.Transactions[0].Outputs[0].Recipient != w.Public().Repr() { t.Fail() } } From e8c8e722d2266110ed3f0014023d107eb9393a0b Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 10 Aug 2017 22:21:40 -0700 Subject: [PATCH 2/2] add broadcast; add pending txns --- app/user.go | 21 +++++++++------------ blockchain/wallet.go | 20 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/user.go b/app/user.go index d507bf1..405950a 100644 --- a/app/user.go +++ b/app/user.go @@ -2,6 +2,7 @@ package app import ( "github.com/ubclaunchpad/cumulus/blockchain" + "github.com/ubclaunchpad/cumulus/msg" crand "crypto/rand" ) @@ -29,14 +30,9 @@ func getCurrentUser() *User { // Pay pays an amount of coin to an address `to`. func (a *App) Pay(to string, amount uint64) error { - // Three atomic steps must occur. - // 1. The amount must be debited from the wallet. - err := a.CurrentUser.Wallet.Debit(amount) - if err == nil { - return err - } + // Two atomic steps must occur. - // 2. A legitimate transaction must be built. + // 1. A legitimate transaction must be built. tbody := blockchain.TxBody{ Sender: getCurrentUser().Wallet.Public(), Outputs: []blockchain.TxOutput{ @@ -47,13 +43,14 @@ func (a *App) Pay(to string, amount uint64) error { }, } - // 3. The transaction must be signed and added to the pool. + // 2. The transaction must be signed and broadcasted. if txn, err := tbody.Sign(*a.CurrentUser.Wallet, crand.Reader); err != nil { - a.HandleTransaction(txn) + a.PeerStore.Broadcast(msg.Push{ + ResourceType: msg.ResourceTransaction, + Resource: txn, + }) + a.CurrentUser.Wallet.SetPending(txn) } else { - // Signature failed, credit the account, return an error. - // Crediting cannot fail because debiting succeeded and process is single threaded. - a.CurrentUser.Wallet.Credit(amount) return err } diff --git a/blockchain/wallet.go b/blockchain/wallet.go index e8c0485..3f578f7 100644 --- a/blockchain/wallet.go +++ b/blockchain/wallet.go @@ -108,7 +108,8 @@ type Account interface { // Wallet is an account that can sign and hold a balance. type Wallet struct { *ecdsa.PrivateKey - Balance uint64 + PendingTxns []*Transaction + Balance uint64 } // Key retreives the underlying private key from a wallet. @@ -168,7 +169,7 @@ func NewWallet() *Wallet { // Debit attempts to spend some coin from the wallet. func (w *Wallet) Debit(amount uint64) error { - if amount >= w.Balance { + if amount >= w.GetEffectiveBalance() { return errors.New("wallet does not have a high enough balance to satisfy the request") } else if amount < 0 { return errors.New("debit amounts may not be negative") @@ -187,3 +188,18 @@ func (w *Wallet) Credit(amount uint64) error { w.Balance += amount return nil } + +// SetPending appends a transaction to the pending set of transactions. +func (w *Wallet) SetPending(t *Transaction) { + w.PendingTxns = append(w.PendingTxns, t) +} + +// GetEffectiveBalance returns the wallet balance less the sum of the pending +// transactions in the wallet. +func (w *Wallet) GetEffectiveBalance() uint64 { + r := uint64(0) + for _, t := range w.PendingTxns { + r += t.Outputs[0].Amount + } + return r +}