Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Transaction Command #120

Merged
merged 10 commits into from
Aug 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 8 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,17 @@ func (a *App) HandleBlock(blk *blockchain.Block) {
// the next block so that the block
// numbers make sense.
a.Chain.AppendBlock(blk)
address := a.CurrentUser.Wallet.Public()
blk := a.Pool.NextBlock(a.Chain, address, a.CurrentUser.BlockSize)

// Drop pending transactions (if they occur in this block).
a.CurrentUser.Wallet.DropAllPending(blk.Transactions)

// Handle miner behaviour (set up a new block).
if miner.IsMining() {
address := a.CurrentUser.Wallet.Public()
blk := a.Pool.NextBlock(a.Chain, address, a.CurrentUser.BlockSize)
miner.RestartMiner(a.Chain, blk)
}

log.Debug("added blk number %d to chain", blk.BlockNumber)
}
}
15 changes: 15 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,18 @@ func TestHandleTransaction(t *testing.T) {
transactionQueue <- blockchain.NewTestTransaction()
assert.Equal(t, len(transactionQueue), 0)
}

func TestPay(t *testing.T) {
amt := uint64(5)
a := createNewTestApp()
err := a.Pay("badf00d", amt)

// Fail with low balance.
assert.NotNil(t, err)

a.CurrentUser.Wallet.SetBalance(amt)
err = a.Pay("badf00d", amt)

// Fail with bad inputs.
assert.NotNil(t, err)
}
62 changes: 38 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,40 @@ 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) {
// Create a new wallet and set as CurrentUser's wallet.
wallet := blockchain.NewWallet()
app.CurrentUser.Wallet = wallet
emoji.Println(":credit_card: New wallet created!")

// Give a printout of the address(es).
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) {
// Read in the recipient address.
emoji.Print(":credit_card:")
ctx.Println(" Enter recipient wallet address")
toAddress := shell.ReadLine()

// Get amount from user.
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
}

// Try to make a payment.
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!")
}
}
69 changes: 56 additions & 13 deletions app/user.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,76 @@
package app

import "github.com/ubclaunchpad/cumulus/blockchain"
import (
"errors"

"github.com/ubclaunchpad/cumulus/blockchain"
"github.com/ubclaunchpad/cumulus/msg"

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to make some changes to this function.

We should:

  1. Create a signed transaction
  2. Broadcast it to all connected nodes

Independently of this function, the balance of the current user's wallet should be computed each time a new block is added to our chain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, so the wallet balance is only updated when our transaction has landed in a block - makes total sense.

// Four steps must occur.
wallet := a.CurrentUser.Wallet
pool := a.Pool

// A legitimate transaction must be built.
tbody := blockchain.TxBody{
Sender: wallet.Public(),
// TODO: Collect inputs.
Input: blockchain.TxHashPointer{},
Outputs: []blockchain.TxOutput{
blockchain.TxOutput{
Recipient: to,
Amount: amount,
},
},
}

// The transaction must be signed.
if txn, err := tbody.Sign(*a.CurrentUser.Wallet, crand.Reader); err == nil {

// The transaction must be broadcasted to the peers.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here doesn't fit what's happening

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed up in inputs PR 😅

if err := wallet.SetPending(txn); err != nil {
return err
}

// The transaction must be added to the pool.
if !pool.Push(txn, a.Chain) {
return errors.New("transaction validation failed")
}

// The transaction must be broadcasted to the network.
a.PeerStore.Broadcast(msg.Push{
ResourceType: msg.ResourceTransaction,
Resource: txn,
})

} else {
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
13 changes: 11 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 Expand Up @@ -103,3 +103,12 @@ func (t *Transaction) InputsEqualOutputs(other ...*Transaction) bool {

return (int(outAmount) - int(inAmount)) == 0
}

// GetTotalOutput sums the output amounts from the transaction.
func (t *Transaction) GetTotalOutput() uint64 {
result := uint64(0)
for _, out := range t.Outputs {
result += out.Amount
}
return result
}
23 changes: 16 additions & 7 deletions blockchain/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,38 @@ 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)
}

func TestTransactionGetTotalOutput(t *testing.T) {
tx := NewTestTransaction()
tx.Outputs = []TxOutput{
TxOutput{
Recipient: tx.Outputs[0].Recipient,
Amount: 5,
},
}
assert.Equal(t, tx.GetTotalOutput(), uint64(5))
}
Loading