Skip to content

Commit

Permalink
added transaction creation at console; removed hotwallet
Browse files Browse the repository at this point in the history
  • Loading branch information
chadlagore committed Aug 10, 2017
1 parent b6d5e37 commit 8c6eb26
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 77 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ run-console: cumulus
deps:
glide install

clean:
clean: cumulus
rm cumulus

install-glide:
Expand Down
58 changes: 34 additions & 24 deletions app/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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!")
}
}
54 changes: 41 additions & 13 deletions app/user.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion blockchain/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
20 changes: 10 additions & 10 deletions blockchain/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewTestTxHashPointer() TxHashPointer {
func NewTestTxOutput() TxOutput {
return TxOutput{
Amount: uint64(mrand.Int63()),
Recipient: NewWallet().Public(),
Recipient: NewWallet().Public().Repr(),
}
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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{
Expand All @@ -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.
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -223,7 +223,7 @@ func NewValidCloudBaseTestTransaction() (*Transaction, Address) {
}
cbReward := TxOutput{
Amount: 25,
Recipient: w.Public(),
Recipient: w.Public().Repr(),
}
cbTxBody := TxBody{
Sender: NilAddr,
Expand Down
4 changes: 2 additions & 2 deletions blockchain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
14 changes: 6 additions & 8 deletions blockchain/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
58 changes: 46 additions & 12 deletions blockchain/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
crand "crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"math"
"math/big"

c "github.com/ubclaunchpad/cumulus/common/constants"
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 8c6eb26

Please sign in to comment.