diff --git a/app/app.go b/app/app.go index faff9be..b72e0aa 100644 --- a/app/app.go +++ b/app/app.go @@ -284,8 +284,8 @@ func (a *App) HandleWork() { // HandleTransaction handles new instance of TransactionWork. func (a *App) HandleTransaction(txn *blockchain.Transaction) { - validTransaction := a.Pool.Push(txn, a.Chain) - if validTransaction { + code := a.Pool.Push(txn, a.Chain) + if code == consensus.ValidTransaction { log.Debug("Added transaction to pool from address: " + txn.Sender.Repr()) } else { log.Debug("Bad transaction rejected from sender: " + txn.Sender.Repr()) @@ -457,7 +457,7 @@ func (a *App) makeBlockRequest(currentHead *blockchain.Block, // handleBlockResponse receives a block or nil from the newBlockChan and attempts // to validate it and add it to the blockchain, or it handles a protocol error -// from the errChan. Returns whether the blockchain was modified and wether we +// from the errChan. Returns whether the blockchain was modified and whether we // received an UpToDate response. func (a *App) handleBlockResponse(newBlockChan chan *blockchain.Block, errChan chan *msg.ProtocolError) (changed bool, upToDate bool) { diff --git a/app/app_test.go b/app/app_test.go index 391603d..d4071e6 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -16,7 +16,7 @@ import ( ) func init() { - log.SetLevel(log.DebugLevel) + log.SetLevel(log.ErrorLevel) } func TestPushHandlerNewBlock(t *testing.T) { @@ -138,7 +138,7 @@ func TestHandleValidBlock(t *testing.T) { bc, blk := blockchain.NewValidTestChainAndBlock() a.Chain = bc a.HandleBlock(blk) - assert.Equal(t, blk, a.Chain.Blocks[2]) + assert.Equal(t, blk, a.Chain.Blocks[3]) // TODO: Assert miner restarted. // TODO: Assert pool appropriately emptied. @@ -226,14 +226,24 @@ func TestHandleBlockResponse(t *testing.T) { a := createNewTestApp() newBlockChan := make(chan *blockchain.Block, 1) errChan := make(chan *msg.ProtocolError, 1) - newBlockChan <- a.Chain.RollBack() + + // Roll the last block off of the chain, add to channel. + lastBlock := a.Chain.RollBack() + assert.NotNil(t, lastBlock) + newBlockChan <- lastBlock + + // Handle the block. changed, upToDate := a.handleBlockResponse(newBlockChan, errChan) assert.True(t, changed) assert.False(t, upToDate) + + // Add an on-chain block to the handler (this shouldn't change the chain). newBlockChan <- a.Chain.Blocks[1] changed, upToDate = a.handleBlockResponse(newBlockChan, errChan) assert.False(t, changed) assert.False(t, upToDate) + + // More stuff happens. errChan <- msg.NewProtocolError(msg.UpToDate, "") changed, upToDate = a.handleBlockResponse(newBlockChan, errChan) assert.False(t, changed) @@ -242,19 +252,30 @@ func TestHandleBlockResponse(t *testing.T) { changed, upToDate = a.handleBlockResponse(newBlockChan, errChan) assert.True(t, changed) assert.False(t, upToDate) - assert.Equal(t, len(a.Chain.Blocks), 1) + assert.Equal(t, len(a.Chain.Blocks), 2) } func TestHandleWork(t *testing.T) { a := createNewTestApp() go a.HandleWork() - a.blockQueue <- a.Chain.RollBack() - time.Sleep(time.Second) + + // Roll the last block off of the chain, add to channel (expect it added back). + lastBlock := a.Chain.RollBack() assert.Equal(t, len(a.Chain.Blocks), 2) + a.blockQueue <- lastBlock + time.Sleep(time.Millisecond * 50) + assert.Equal(t, len(a.Chain.Blocks), 3) + + // Kill the worker. a.quitChan <- true - a.blockQueue <- a.Chain.RollBack() - time.Sleep(time.Second) - assert.Equal(t, len(a.Chain.Blocks), 1) + + // Roll the last block off of the chain, add to channel + // (expect it not to be added back). + lastBlock = a.Chain.RollBack() + assert.Equal(t, len(a.Chain.Blocks), 2) + a.blockQueue <- lastBlock + time.Sleep(time.Millisecond * 50) + assert.Equal(t, len(a.Chain.Blocks), 2) } func TestPay(t *testing.T) { diff --git a/app/user.go b/app/user.go index 17dc5e7..b139468 100644 --- a/app/user.go +++ b/app/user.go @@ -3,6 +3,8 @@ package app import ( "errors" + "github.com/ubclaunchpad/cumulus/consensus" + "github.com/ubclaunchpad/cumulus/blockchain" "github.com/ubclaunchpad/cumulus/msg" @@ -41,7 +43,7 @@ func (a *App) Pay(to string, amount uint64) error { tbody := blockchain.TxBody{ Sender: wallet.Public(), // TODO: Collect inputs. - Input: blockchain.TxHashPointer{}, + Inputs: []blockchain.TxHashPointer{}, Outputs: []blockchain.TxOutput{ blockchain.TxOutput{ Recipient: to, @@ -59,7 +61,8 @@ func (a *App) Pay(to string, amount uint64) error { } // The transaction must be added to the pool. - if !pool.Push(txn, a.Chain) { + code := pool.Push(txn, a.Chain) + if code != consensus.ValidTransaction { return errors.New("transaction validation failed") } diff --git a/blockchain/block.go b/blockchain/block.go index 1655d7f..b2026a5 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -69,7 +69,7 @@ func (b *Block) Len() int { } // Marshal converts a Block to a byte slice. -func (b *Block) Marshal() []byte { +func (b Block) Marshal() []byte { var buf []byte buf = append(buf, b.BlockHeader.Marshal()...) for _, t := range b.Transactions { diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 4267671..dea2e4e 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -53,17 +53,32 @@ func (bc *BlockChain) LastBlock() *Block { return bc.Blocks[len(bc.Blocks)-1] } -// GetInputTransaction returns the input Transaction to t. If the input does -// not exist, then GetInputTransaction returns nil. -func (bc *BlockChain) GetInputTransaction(t *Transaction) *Transaction { - if t.Input.BlockNumber > uint32(len(bc.Blocks)) { +// GetInputTransaction returns the input Transaction referenced by TxHashPointer. +// If the Transaction does not exist, then GetInputTransaction returns nil. +func (bc *BlockChain) GetInputTransaction(t *TxHashPointer) *Transaction { + if t.BlockNumber > uint32(len(bc.Blocks)) { return nil } - b := bc.Blocks[t.Input.BlockNumber] - if t.Input.Index > uint32(len(b.Transactions)) { + b := bc.Blocks[t.BlockNumber] + if t.Index > uint32(len(b.Transactions)) { return nil } - return b.Transactions[t.Input.Index] + return b.Transactions[t.Index] +} + +// GetAllInputs returns all the transactions referenced by a transaction +// as inputs. Returns an error if any of the transactios requested could +// not be found. +func (bc *BlockChain) GetAllInputs(t *Transaction) ([]*Transaction, error) { + txns := []*Transaction{} + for _, tx := range t.Inputs { + nextTxn := bc.GetInputTransaction(&tx) + if nextTxn == nil { + return nil, errors.New("input transaction not found") + } + txns = append(txns, nextTxn) + } + return txns, nil } // ContainsTransaction returns true if the BlockChain contains the transaction diff --git a/blockchain/genesis.go b/blockchain/genesis.go index 2cc99a8..4253265 100644 --- a/blockchain/genesis.go +++ b/blockchain/genesis.go @@ -15,15 +15,16 @@ func Genesis(miner Address, target Hash, blockReward uint64, extraData []byte) * Amount: blockReward, Recipient: miner.Repr(), } + cbInput := TxHashPointer{ + BlockNumber: 0, + Hash: NilHash, + Index: 0, + } cbTx := &Transaction{ TxBody: TxBody{ - Sender: NilAddr, - Input: TxHashPointer{ - BlockNumber: 0, - Hash: NilHash, - Index: 0, - }, + Sender: NilAddr, + Inputs: []TxHashPointer{cbInput}, Outputs: []TxOutput{cbReward}, }, Sig: NilSig, diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index 738625c..bcf76ad 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -38,14 +38,18 @@ func NewTestTxOutput() TxOutput { func NewTestTxBody() TxBody { // Uniform distribution on [1, 4] nOutputs := mrand.Intn(4) + 1 + nInputs := mrand.Intn(4) + 1 body := TxBody{ Sender: NewWallet().Public(), - Input: NewTestTxHashPointer(), + Inputs: make([]TxHashPointer, nInputs), Outputs: make([]TxOutput, nOutputs), } for i := 0; i < nOutputs; i++ { body.Outputs[i] = NewTestTxOutput() } + for i := 0; i < nInputs; i++ { + body.Inputs[i] = NewTestTxHashPointer() + } return body } @@ -123,82 +127,217 @@ 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) { +// NewTestTransactionValue creates a new transaction with specific value a at +// index i in block number b. +func NewTestTransactionValue(s, r *Wallet, a uint64, i uint32, b uint32) (*Transaction, error) { tbody := TxBody{ - Sender: s.Public(), - Input: TxHashPointer{ - BlockNumber: 0, - Hash: NewTestHash(), - Index: i, - }, + Sender: s.Public(), + Inputs: make([]TxHashPointer, 1), Outputs: make([]TxOutput, 1), } tbody.Outputs[0] = TxOutput{ Amount: a, Recipient: r.Public().Repr(), } + tbody.Inputs[0] = TxHashPointer{ + BlockNumber: b, + Hash: NewTestHash(), + Index: i, + } return tbody.Sign(*s, crand.Reader) } -// NewValidBlockChainFixture creates a valid blockchain of two blocks. -func NewValidBlockChainFixture() (*BlockChain, Wallet) { - original := NewWallet() +// NewValidBlockChainFixture creates a valid blockchain of three blocks +// and returns the wallets involved in the transactions. +// The returning wallets will have balances of 3, 1, and 0 respectively. +func NewValidBlockChainFixture() (*BlockChain, map[string]*Wallet) { sender := NewWallet() - recipient := NewWallet() + alice := NewWallet() + bob := NewWallet() - trA, _ := NewTestTransactionValue(original, sender, 2, 1) - trA.Outputs = append(trA.Outputs, TxOutput{ - Amount: 2, - Recipient: sender.Public().Repr(), - }) + // Cloud base txns for our blocks. + cbA, _ := NewValidCloudBaseTestTransaction() + cbB, _ := NewValidCloudBaseTestTransaction() + cbC, _ := NewValidCloudBaseTestTransaction() - trB, _ := NewTestTransactionValue(sender, recipient, 4, 1) - trB.Input.Hash = HashSum(trA) + // Transaction A is at index 1 in block 0 (sender awarded 4 coins). + tA, _ := TxBody{ + Sender: sender.Public(), + Inputs: []TxHashPointer{}, + Outputs: []TxOutput{ + TxOutput{ + Amount: 2, + Recipient: sender.Public().Repr(), + }, + TxOutput{ + Amount: 2, + Recipient: sender.Public().Repr(), + }, + }, + }.Sign(*sender, crand.Reader) - trB, _ = trB.TxBody.Sign(*sender, crand.Reader) + block0 := Block{ + BlockHeader: BlockHeader{ + BlockNumber: 0, + LastBlock: NewTestHash(), + Target: NewValidTestTarget(), + Time: mrand.Uint32(), + Nonce: 0, + }, + // Block0 is a cb and a transaction. + Transactions: []*Transaction{cbA, tA}, + } - cbA, _ := NewValidCloudBaseTestTransaction() - cbB, _ := NewValidCloudBaseTestTransaction() - inputTransactions := []*Transaction{cbA, trA} - outputTransactions := []*Transaction{cbB, trB} + // Transaction B is at index 1 in block 1 (sender sends 3 coins to alice). + tB, _ := TxBody{ + Sender: sender.Public(), + Inputs: []TxHashPointer{ + TxHashPointer{ + // Reference block 0, index 0 for inputs. + BlockNumber: 0, + Index: 1, // Cloudbase will bump transactions forward. + Hash: HashSum(tA), + }, + }, + // Send some outputs to alice, some back to sender. + Outputs: []TxOutput{ + TxOutput{ + Amount: 3, + Recipient: alice.Public().Repr(), + }, + TxOutput{ + Amount: 1, + Recipient: sender.Public().Repr(), + }, + }, + }.Sign(*sender, crand.Reader) + + // Block1 is a cb and a transaction. + block1 := Block{ + BlockHeader: BlockHeader{ + BlockNumber: 1, + LastBlock: HashSum(block0), + Target: NewValidTestTarget(), + Time: mrand.Uint32(), + Nonce: 0, + }, + Transactions: []*Transaction{cbB, tB}, + } - inputBlock := NewTestInputBlock(inputTransactions) - outputBlock := NewTestOutputBlock(outputTransactions, inputBlock) + // Sender has 1 coin left to send to bob. + tC, _ := TxBody{ + Sender: sender.Public(), + Inputs: []TxHashPointer{ + TxHashPointer{ + // Again look at block 1. + BlockNumber: 1, + Index: 1, // skip cb + Hash: HashSum(tB), + }, + }, + // One coin output to bob. + Outputs: []TxOutput{ + TxOutput{ + Amount: 1, + Recipient: bob.Public().Repr(), + }, + }, + }.Sign(*sender, crand.Reader) + + // Block2 is a cb and a transaction. + block2 := Block{ + BlockHeader: BlockHeader{ + BlockNumber: 2, + LastBlock: HashSum(block1), + Target: NewValidTestTarget(), + Time: mrand.Uint32(), + Nonce: 0, + }, + Transactions: []*Transaction{cbC, tC}, + } + + wallets := map[string]*Wallet{ + "alice": alice, + "bob": bob, + "sender": sender, + } return &BlockChain{ - Blocks: []*Block{inputBlock, outputBlock}, + Blocks: []*Block{&block0, &block1, &block2}, Head: NewTestHash(), - }, *recipient + }, wallets // Wallet balances 3, 1, 0. } -// NewValidTestChainAndBlock creates a valid BlockChain and a Block that is valid -// with respect to the BlockChain. +// NewValidTestChainAndBlock creates a valid BlockChain of 3 blocks, +// and a new block which is valid with respect to the blockchain. func NewValidTestChainAndBlock() (*BlockChain, *Block) { - bc, s := NewValidBlockChainFixture() - inputBlock := bc.Blocks[1] - inputTransaction := inputBlock.Transactions[0] - a := inputTransaction.Outputs[0].Amount + bc, wallets := NewValidBlockChainFixture() - // Create a legit block that does *not* appear in bc. - tbody := TxBody{ - Sender: s.Public(), - Input: TxHashPointer{ - BlockNumber: 1, - Hash: HashSum(inputTransaction), - Index: 0, + // Alice wants to send 2 coins to bob and bob wants to send + // his coin back to the sender. + aliceToBob, _ := TxBody{ + Sender: wallets["alice"].Public(), + Inputs: []TxHashPointer{ + TxHashPointer{ + // Block 1, transaction 1 is where this input comes from. + BlockNumber: 1, + Index: 1, + Hash: HashSum(bc.Blocks[1].Transactions[1]), + }, }, - Outputs: make([]TxOutput, 1), - } - tbody.Outputs[0] = TxOutput{ - Amount: a, - Recipient: NewWallet().Public().Repr(), - } + // One output to bob, one back to alice. + Outputs: []TxOutput{ + TxOutput{ + Amount: 2, + Recipient: wallets["bob"].Public().Repr(), + }, + TxOutput{ + Amount: 1, + Recipient: wallets["alice"].Public().Repr(), + }, + }, + }.Sign(*wallets["alice"], crand.Reader) + + bobToSender, _ := TxBody{ + Sender: wallets["bob"].Public(), + Inputs: []TxHashPointer{ + TxHashPointer{ + // Block 2, transaction 1 is where this input comes from. + BlockNumber: 2, + Index: 1, + Hash: HashSum(bc.Blocks[2].Transactions[1]), + }, + }, + // One output to sender. + Outputs: []TxOutput{ + TxOutput{ + Amount: 1, + Recipient: wallets["sender"].Public().Repr(), + }, + }, + }.Sign(*wallets["bob"], crand.Reader) - tr, _ := tbody.Sign(s, crand.Reader) cb, _ := NewValidCloudBaseTestTransaction() - newBlock := NewTestOutputBlock([]*Transaction{cb, tr}, inputBlock) - return bc, newBlock + + blk := Block{ + BlockHeader: BlockHeader{ + BlockNumber: 3, + LastBlock: HashSum(bc.Blocks[2]), + Target: NewValidTestTarget(), + Time: mrand.Uint32(), + Nonce: 0, + }, + Transactions: []*Transaction{cb, aliceToBob, bobToSender}, + } + + return bc, &blk +} + +// NewValidChainAndTxn creates a valid BlockChain of 3 blocks, +// and a Transaction that is valid with respect to the BlockChain. +func NewValidChainAndTxn() (*BlockChain, *Transaction) { + bc, b := NewValidTestChainAndBlock() + return bc, b.Transactions[1] } // NewValidTestTarget creates a new valid target that is a random value between the @@ -227,7 +366,7 @@ func NewValidCloudBaseTestTransaction() (*Transaction, Address) { } cbTxBody := TxBody{ Sender: NilAddr, - Input: cbInput, + Inputs: []TxHashPointer{cbInput}, Outputs: []TxOutput{cbReward}, } cbTx := &Transaction{ diff --git a/blockchain/transaction.go b/blockchain/transaction.go index c7e963f..ca70982 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -3,8 +3,10 @@ package blockchain import ( "encoding/binary" "io" + "math" "github.com/ubclaunchpad/cumulus/common/util" + "gopkg.in/fatih/set.v0" ) // TxHashPointer is a reference to a transaction on the blockchain. @@ -40,7 +42,7 @@ func (to TxOutput) Marshal() []byte { // TxBody contains all relevant information about a transaction type TxBody struct { Sender Address - Input TxHashPointer + Inputs []TxHashPointer Outputs []TxOutput } @@ -53,7 +55,9 @@ func (tb TxBody) Len() int { func (tb TxBody) Marshal() []byte { var buf []byte buf = append(buf, tb.Sender.Marshal()...) - buf = append(buf, tb.Input.Marshal()...) + for _, in := range tb.Inputs { + buf = append(buf, in.Marshal()...) + } for _, out := range tb.Outputs { buf = append(buf, out.Marshal()...) } @@ -112,3 +116,93 @@ func (t *Transaction) GetTotalOutput() uint64 { } return result } + +// GetTotalOutputFor sums the outputs referenced to a specific recipient. +// recipient is an address checksum hex string. +func (t *Transaction) GetTotalOutputFor(recipient string) uint64 { + result := uint64(0) + for _, out := range t.Outputs { + if out.Recipient == recipient { + result += out.Amount + } + } + return result +} + +// GetTotalInput sums the input amounts from the transaction. +// Requires the blockchain for lookups. +func (t *Transaction) GetTotalInput(bc *BlockChain) (uint64, error) { + + result := uint64(0) + // This is a bit crazy; filter all input transactions + // by this senders address and sum the outputs. + inputs, err := bc.GetAllInputs(t) + if err != nil { + return 0, err + } + for _, in := range inputs { + result += in.GetTotalOutputFor(t.Sender.Repr()) + } + return result, nil +} + +// GetBlockRange returns the start and end block indexes for the inputs +// to a transaction. +func (bc *BlockChain) GetBlockRange(t *Transaction) (uint32, uint32) { + min := uint32(math.MaxUint32) + max := uint32(0) + for _, in := range t.Inputs { + if in.BlockNumber < min { + min = in.BlockNumber + } + if in.BlockNumber > max { + max = in.BlockNumber + } + } + return min, max +} + +// InputsIntersect returns true if the inputs of t intersect with those of other. +func (t *Transaction) InputsIntersect(other *Transaction) bool { + return !t.InputIntersection(other).IsEmpty() +} + +// InputIntersection returns the intersection of the inputs of t and other. +func (t *Transaction) InputIntersection(other *Transaction) set.Interface { + return set.Intersection(t.InputSet(), other.InputSet()) +} + +// InputSet returns the transaction inputs as a set object. +func (t *Transaction) InputSet() *set.Set { + a := make([]interface{}, len(t.Inputs)) + for i, v := range t.Inputs { + a[i] = v + } + return set.New(a...) +} + +// InputsSpentElsewhere returns true if inputs purported to be only spent +// on transaction t have been spent elsewhere after block index `start`. +func (t *Transaction) InputsSpentElsewhere(bc *BlockChain, start uint32) bool { + // Get the set of inputs for t. + inSet := t.InputSet() + + // Look at each transaction in the chain from start on. + for _, b := range bc.Blocks[start:] { + for _, txn := range b.Transactions { + + // If the inputs to t intersect with the inputs to txn... + if !set.Intersection(inSet, txn.InputSet()).IsEmpty() { + + // ... and the sender is the same, then we have a respend. + if txn.Sender.Repr() == t.Sender.Repr() { + return true + } + } + } + } + + // If we made it through all the transactions, without finding + // inputs respent anywhere, then we're good. + return false +} diff --git a/blockchain/transaction_test.go b/blockchain/transaction_test.go index b9d1d53..6b3a9e3 100644 --- a/blockchain/transaction_test.go +++ b/blockchain/transaction_test.go @@ -9,7 +9,7 @@ import ( func TestTxBodyLen(t *testing.T) { txBody := NewTestTxBody() senderLen := AddrLen - inputLen := 2*(32/8) + HashLen + inputLen := len(txBody.Inputs) * (2*(32/8) + HashLen) outputLen := len(txBody.Outputs) * (64/8 + ReprLen) txBodyLen := senderLen + inputLen + outputLen @@ -19,7 +19,7 @@ func TestTxBodyLen(t *testing.T) { func TestTransactionLen(t *testing.T) { tx := NewTestTransaction() senderLen := AddrLen - inputLen := 2*(32/8) + HashLen + inputLen := len(tx.TxBody.Inputs) * (2*(32/8) + HashLen) outputLen := len(tx.TxBody.Outputs) * (64/8 + ReprLen) txBodyLen := senderLen + inputLen + outputLen txLen := txBodyLen + SigLen @@ -37,3 +37,36 @@ func TestTransactionGetTotalOutput(t *testing.T) { } assert.Equal(t, tx.GetTotalOutput(), uint64(5)) } + +func TestInputSet(t *testing.T) { + txn := NewTestTransaction() + inSet := txn.InputSet() + assert.Equal(t, inSet.Size(), len(txn.Inputs)) + for _, tx := range txn.Inputs { + assert.True(t, inSet.Has(tx)) + } +} + +func TestInputIntersection(t *testing.T) { + txn := NewTestTransaction() + ixn := txn.InputIntersection(txn) + assert.Equal(t, ixn.Size(), len(txn.Inputs)) + for _, tx := range txn.Inputs { + assert.True(t, ixn.Has(tx)) + } + assert.True(t, txn.InputsIntersect(txn)) +} + +func TestGetTotalOutputFor(t *testing.T) { + bc, wallets := NewValidBlockChainFixture() + + // We know alice gets sent 3 coins in block 1, txn 1. + t1 := bc.Blocks[1].Transactions[1] + actual := t1.GetTotalOutputFor(wallets["alice"].Public().Repr()) + assert.Equal(t, actual, uint64(3)) + + // We know bob gets sent 1 coins in block 2, txn 1. + t2 := bc.Blocks[2].Transactions[1] + actual = t2.GetTotalOutputFor(wallets["bob"].Public().Repr()) + assert.Equal(t, actual, uint64(1)) +} diff --git a/blockchain/wallet.go b/blockchain/wallet.go index ecbf18c..c885459 100644 --- a/blockchain/wallet.go +++ b/blockchain/wallet.go @@ -199,7 +199,7 @@ func (w *Wallet) DropAllPending(txns []*Transaction) { // DropPending a single pending transaction by index in the pending list. func (w *Wallet) DropPending(i int) { if i < len(w.PendingTxns) && i >= 0 { - log.Info("dropping transaction with hash %s", w.PendingTxns[i].Input.Hash) + log.Info("dropping transaction with hash %s", HashSum(w.PendingTxns[i])) w.PendingTxns = append(w.PendingTxns[:i], w.PendingTxns[i+1:]...) } } @@ -208,7 +208,7 @@ func (w *Wallet) DropPending(i int) { // If true, it also returns the integer index of the transaction. func (w *Wallet) IsPending(txn *Transaction) (bool, int) { for i, t := range w.PendingTxns { - if t.Input.Hash == txn.Input.Hash { + if HashSum(t) == HashSum(txn) { return true, i } } diff --git a/consensus/consensus.go b/consensus/consensus.go index 42d9f5e..52de19c 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -5,6 +5,8 @@ import ( "math" "reflect" + "gopkg.in/fatih/set.v0" + log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/blockchain" c "github.com/ubclaunchpad/cumulus/common/constants" @@ -20,26 +22,27 @@ func VerifyTransaction(bc *blockchain.BlockChain, } // Find the transaction input in the chain (by hash) - input := bc.GetInputTransaction(t) - if input == nil || blockchain.HashSum(input) != t.Input.Hash { - return false, NoInputTransaction + inputs, err := bc.GetAllInputs(t) + if err != nil || len(inputs) == 0 { + return false, NoInputTransactions } // Check that output to sender in input is equal to outputs in t - if !input.InputsEqualOutputs(t) { + out := t.GetTotalOutput() + in, err := t.GetTotalInput(bc) + if out != in { return false, Overspend } - // Verify signature of t + // Verify signature of t. hash := blockchain.HashSum(t.TxBody) if !ecdsa.Verify(t.Sender.Key(), hash.Marshal(), t.Sig.R, t.Sig.S) { return false, BadSig } - // Test if identical transaction already exists in chain. - end := uint32(len(bc.Blocks)) - start := t.Input.BlockNumber - if exists, _, _ := bc.ContainsTransaction(t, start, end); exists { + // Test if inputs already spent elsewhere. + start, _ := bc.GetBlockRange(t) + if t.InputsSpentElsewhere(bc, start) { return false, Respend } @@ -61,10 +64,10 @@ func VerifyCloudBase(bc *blockchain.BlockChain, return false, BadCloudBaseSender } - // Check that the input is 0. - if t.TxBody.Input.BlockNumber != 0 || - t.TxBody.Input.Hash != blockchain.NilHash || - t.Input.Index != 0 { + // Check that the input is 0 (only one input to CB). + input := t.TxBody.Inputs[0] + if input.BlockNumber != 0 || input.Hash != blockchain.NilHash || + input.Index != 0 { return false, BadCloudBaseInput } @@ -170,14 +173,14 @@ func VerifyBlock(bc *blockchain.BlockChain, } // Check that block number is valid. - ix := b.BlockNumber - 1 - if int(ix) > len(bc.Blocks)-1 || ix < 0 { + lastBlockIdx := b.BlockNumber - 1 + if int(lastBlockIdx) != len(bc.Blocks)-1 { return false, BadBlockNumber } // Check that block number is one greater than last block - lastBlock := bc.Blocks[ix] - if lastBlock.BlockNumber != ix { + lastBlock := bc.Blocks[lastBlockIdx] + if lastBlock.BlockNumber != lastBlockIdx { return false, BadBlockNumber } @@ -221,15 +224,27 @@ func VerifyBlock(bc *blockchain.BlockChain, return false, BadNonce } - // Check for multiple transactions referencing same input transaction. - for i, trA := range b.Transactions { - for j, trB := range b.Transactions { - if (i != j) && (trA.Input.Hash == trB.Input.Hash) { - inputTr := bc.GetInputTransaction(trA) - if !inputTr.InputsEqualOutputs(trA, trB) { - return false, DoubleSpend - } + // Check for multiple transactions referencing same input transaction, + // where the sender is the same. + inputSets := map[blockchain.Address]*set.Set{} + for _, txn := range b.Transactions { + + // We'll inspect these inputs next. + nextInputSet := txn.InputSet() + + // If the sender already exists in the map, check for + // a non-empty intersection in inputs. + if inSet, ok := inputSets[txn.Sender]; ok { + if !set.Intersection(inSet, nextInputSet).IsEmpty() { + return false, DoubleSpend } + + // No intersection, but more inputs to add to sender. + inputSets[txn.Sender].Merge(nextInputSet) + + } else { + // First time seeing sender, give them inputs. + inputSets[txn.Sender] = nextInputSet } } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 389ffce..87b9b8d 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -1,12 +1,12 @@ package consensus import ( - "fmt" "math/rand" "testing" crand "crypto/rand" + "github.com/stretchr/testify/assert" "github.com/ubclaunchpad/cumulus/blockchain" c "github.com/ubclaunchpad/cumulus/common/constants" "github.com/ubclaunchpad/cumulus/common/util" @@ -19,31 +19,18 @@ func TestVerifyTransactionNilTransaction(t *testing.T) { valid, code := VerifyTransaction(bc, nil) - if valid { - t.Fail() - } - if code != NilTransaction { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, NilTransaction) } func TestVerifyTransactionNoInputTransaction(t *testing.T) { - tr, _ := blockchain.NewTestTransactionValue( - blockchain.NewWallet(), - blockchain.NewWallet(), - 1, - 0, - ) + txn := blockchain.NewTestTransaction() bc, _ := blockchain.NewValidBlockChainFixture() + txn.Inputs = []blockchain.TxHashPointer{} + valid, code := VerifyTransaction(bc, txn) - valid, code := VerifyTransaction(bc, tr) - - if valid { - t.Fail() - } - if code != NoInputTransaction { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, NoInputTransactions) } func TestVerifyTransactionOverspend(t *testing.T) { @@ -54,60 +41,40 @@ func TestVerifyTransactionOverspend(t *testing.T) { valid, code := VerifyTransaction(bc, tr) - if valid { - t.Fail() - } - if code != Overspend { - fmt.Println(code) - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, Overspend) } func TestVerifyTransactionSignatureFail(t *testing.T) { - bc, _ := blockchain.NewValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[1] + bc, txn := blockchain.NewValidChainAndTxn() + // Resign txn with fake wallet. fakeSender := blockchain.NewWallet() - tr, _ = tr.TxBody.Sign(*fakeSender, crand.Reader) - bc.Blocks[1].Transactions[1] = tr + txn, _ = txn.TxBody.Sign(*fakeSender, crand.Reader) - valid, code := VerifyTransaction(bc, tr) - if valid { - t.Fail() - } - if code != BadSig { - t.Fail() - } + valid, code := VerifyTransaction(bc, txn) + + assert.False(t, valid) + assert.Equal(t, code, BadSig) } func TestVerifyTransactionPass(t *testing.T) { - bc, b := blockchain.NewValidTestChainAndBlock() - tr := b.Transactions[1] + bc, txn := blockchain.NewValidChainAndTxn() - valid, code := VerifyTransaction(bc, tr) + valid, code := VerifyTransaction(bc, txn) - if !valid { - t.Fail() - } - if code != ValidTransaction { - t.Fail() - } + assert.True(t, valid) + assert.Equal(t, code, ValidTransaction) } func TestTransactionRespend(t *testing.T) { - bc, _ := blockchain.NewValidBlockChainFixture() - trC := bc.Blocks[1].Transactions[1] - b := blockchain.NewTestOutputBlock([]*blockchain.Transaction{trC}, bc.Blocks[1]) - bc.AppendBlock(b) + bc, _ := blockchain.NewValidTestChainAndBlock() + trC := bc.Blocks[2].Transactions[1] valid, code := VerifyTransaction(bc, trC) - if valid { - t.Fail() - } - if code != Respend { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, Respend) } // VerifyBlock Tests @@ -117,12 +84,8 @@ func TestVerifyBlockNilBlock(t *testing.T) { valid, code := VerifyBlock(bc, nil) - if valid { - t.Fail() - } - if code != NilBlock { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, NilBlock) } func TestVerifyBlockBadNonce(t *testing.T) { @@ -132,13 +95,8 @@ func TestVerifyBlockBadNonce(t *testing.T) { valid, code := VerifyBlock(bc, b) CurrentDifficulty = c.MinTarget - if valid { - t.Fail() - } - - if code != BadNonce { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, BadNonce) } func TestVerifyBlockBadGenesisBlock(t *testing.T) { @@ -153,28 +111,20 @@ func TestVerifyBlockBadGenesisBlock(t *testing.T) { valid, code := VerifyBlock(bc, gb) - if valid { - t.Fail() - } - - if code != BadGenesisBlock { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, BadGenesisBlock) } func TestVerifyBlockBadTransaction(t *testing.T) { - bc, _ := blockchain.NewValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[1] - tr.Outputs[0].Amount = 5 + bc, b := blockchain.NewValidTestChainAndBlock() - valid, code := VerifyBlock(bc, bc.Blocks[1]) + // This would be an overspend on alices part (she only has 3 coins here). + b.Transactions[1].Outputs[0].Amount = 5 - if valid { - t.Fail() - } - if code != BadTransaction { - t.Fail() - } + valid, code := VerifyBlock(bc, b) + + assert.False(t, valid) + assert.Equal(t, code, BadTransaction) } func TestVerifyBlockBadBlockNumber(t *testing.T) { @@ -183,12 +133,8 @@ func TestVerifyBlockBadBlockNumber(t *testing.T) { valid, code := VerifyBlock(bc, bc.Blocks[1]) - if valid { - t.Fail() - } - if code != BadBlockNumber { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, BadBlockNumber) } func TestVerifyBlockBadHash(t *testing.T) { @@ -262,12 +208,8 @@ func TestBlockDoubleSpend(t *testing.T) { valid, code := VerifyBlock(bc, b) - if valid { - t.Fail() - } - if code != DoubleSpend { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, DoubleSpend) } func TestVerifyBlockBigNumber(t *testing.T) { @@ -360,7 +302,7 @@ func TestVerifyCloudBaseBadSender(t *testing.T) { func TestVerifyCloudBaseBadBadInput(t *testing.T) { bc, _ := blockchain.NewValidBlockChainFixture() b := bc.Blocks[0] - b.GetCloudBaseTransaction().TxBody.Input.BlockNumber = 1 + b.GetCloudBaseTransaction().TxBody.Inputs[0].BlockNumber = 1 valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) if valid { @@ -372,7 +314,7 @@ func TestVerifyCloudBaseBadBadInput(t *testing.T) { bc, _ = blockchain.NewValidBlockChainFixture() b = bc.Blocks[0] - b.GetCloudBaseTransaction().TxBody.Input.Hash = blockchain.NewTestHash() + b.GetCloudBaseTransaction().TxBody.Inputs[0].Hash = blockchain.NewTestHash() valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) if valid { @@ -384,7 +326,7 @@ func TestVerifyCloudBaseBadBadInput(t *testing.T) { bc, _ = blockchain.NewValidBlockChainFixture() b = bc.Blocks[0] - b.GetCloudBaseTransaction().TxBody.Input.Index = 1 + b.GetCloudBaseTransaction().TxBody.Inputs[0].Index = 1 valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) if valid { diff --git a/consensus/errors.go b/consensus/errors.go index c6f81f6..a46f99a 100644 --- a/consensus/errors.go +++ b/consensus/errors.go @@ -15,8 +15,8 @@ type BlockCode uint32 const ( // ValidTransaction is returned when transaction is valid. ValidTransaction TransactionCode = iota - // NoInputTransaction is returned when transaction has no valid input transaction. - NoInputTransaction + // NoInputTransactions is returned when transaction has no valid input transaction. + NoInputTransactions // Overspend is returned when transaction outputs exceed transaction inputs. Overspend // BadSig is returned when the signature verification fails. diff --git a/glide.lock b/glide.lock index 0dd1512..07623ea 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: b8d3d8395d2867faced0c31743cd56384beb07b6290c0e339c84d2354de4fcbc -updated: 2017-07-29T17:35:29.15377758-07:00 +hash: e768a54d8cc1f394193c672f3d61ab653cf65f1174bb7799586ed46257afeb91 +updated: 2017-08-25T21:54:14.356471973-07:00 imports: - name: github.com/abiosoft/ishell version: a24a06e34a7a9d8f894fb339b3e77333e4fc75ac @@ -71,6 +71,8 @@ imports: subpackages: - transform - unicode/norm +- name: gopkg.in/fatih/set.v0 + version: 27c40922c40b43fe04554d8223a402af3ea333f3 - name: gopkg.in/kyokomi/emoji.v1 version: 7e06b236c489543f53868841f188a294e3383eab - name: gopkg.in/yaml.v2 diff --git a/glide.yaml b/glide.yaml index a5ed419..d910c0f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -13,3 +13,5 @@ import: - package: github.com/abiosoft/ishell - package: gopkg.in/kyokomi/emoji.v1 version: ^1.5.0 +- package: gopkg.in/fatih/set.v0 + version: ^0.1.0 diff --git a/miner/miner.go b/miner/miner.go index 9518c1e..0cd152b 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -107,7 +107,7 @@ func CloudBase( } cbTxBody := blockchain.TxBody{ Sender: blockchain.NilAddr, - Input: cbInput, + Inputs: []blockchain.TxHashPointer{cbInput}, Outputs: []blockchain.TxOutput{cbReward}, } cbTx := blockchain.Transaction{ @@ -117,14 +117,6 @@ func CloudBase( b.Transactions = append([]*blockchain.Transaction{&cbTx}, b.Transactions...) - // Increment the input index of every transaction that has an input in the - // new block - for _, tx := range b.Transactions[1:] { - if tx.Input.BlockNumber == uint32(len(bc.Blocks)) { - tx.Input.Index++ - } - } - return b } diff --git a/pool/pool.go b/pool/pool.go index 626f5d2..76e253c 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -3,15 +3,13 @@ package pool import ( "time" - log "github.com/Sirupsen/logrus" - "github.com/ubclaunchpad/cumulus/blockchain" "github.com/ubclaunchpad/cumulus/common/util" "github.com/ubclaunchpad/cumulus/consensus" "github.com/ubclaunchpad/cumulus/miner" ) -// PooledTransaction is a Transaction with a timetamp. +// PooledTransaction is a Transaction with a timestamp. type PooledTransaction struct { Transaction *blockchain.Transaction Time time.Time @@ -31,14 +29,14 @@ func New() *Pool { } } -// Len returns the number of transactions in the Pool. -func (p *Pool) Len() int { +// Size returns the number of transactions in the Pool. +func (p *Pool) Size() int { return len(p.ValidTransactions) } // Empty returns true if the pool has exactly zero transactions in it. func (p *Pool) Empty() bool { - return p.Len() == 0 + return p.Size() == 0 } // Get returns the tranasction with input transaction Hash h. @@ -53,8 +51,9 @@ func (p *Pool) GetN(N int) *blockchain.Transaction { // GetIndex returns the index of the transaction in the ordering. func (p *Pool) GetIndex(t *blockchain.Transaction) int { - target := p.ValidTransactions[t.Input.Hash].Time - return getIndex(p.Order, target, 0, p.Len()-1) + hash := blockchain.HashSum(t) + target := p.ValidTransactions[hash].Time + return getIndex(p.Order, target, 0, p.Size()-1) } // getIndex does a binary search for a PooledTransaction by timestamp. @@ -72,14 +71,12 @@ func getIndex(a []*PooledTransaction, target time.Time, low, high int) int { // Push inserts a transaction into the pool, returning // true if the Transaction was inserted (was valid). // TODO: This should return an error if could not add. -func (p *Pool) Push(t *blockchain.Transaction, bc *blockchain.BlockChain) bool { - if ok, err := consensus.VerifyTransaction(bc, t); ok { +func (p *Pool) Push(t *blockchain.Transaction, bc *blockchain.BlockChain) consensus.TransactionCode { + ok, code := consensus.VerifyTransaction(bc, t) + if ok { p.set(t) - return true - } else { - log.Debug(err) - return false } + return code } // PushUnsafe adds a transaction to the pool without validation. @@ -88,10 +85,10 @@ func (p *Pool) PushUnsafe(t *blockchain.Transaction) { } // Silently adds a transaction to the pool. -// Deletes a transaction if it exists from the same -// input hash. +// Deletes a transaction if it exists from the input hash. func (p *Pool) set(t *blockchain.Transaction) { - if txn, ok := p.ValidTransactions[t.Input.Hash]; ok { + hash := blockchain.HashSum(t) + if txn, ok := p.ValidTransactions[hash]; ok { p.Delete(txn.Transaction) } vt := &PooledTransaction{ @@ -99,16 +96,17 @@ func (p *Pool) set(t *blockchain.Transaction) { Time: time.Now(), } p.Order = append(p.Order, vt) - p.ValidTransactions[t.Input.Hash] = vt + p.ValidTransactions[hash] = vt } // Delete removes a transaction from the Pool. func (p *Pool) Delete(t *blockchain.Transaction) { - vt, ok := p.ValidTransactions[t.Input.Hash] + hash := blockchain.HashSum(t) + vt, ok := p.ValidTransactions[hash] if ok { i := p.GetIndex(vt.Transaction) p.Order = append(p.Order[0:i], p.Order[i+1:]...) - delete(p.ValidTransactions, t.Input.Hash) + delete(p.ValidTransactions, hash) } } @@ -127,7 +125,7 @@ func (p *Pool) Update(b *blockchain.Block, bc *blockchain.BlockChain) bool { // Pop returns the next transaction and removes it from the pool. func (p *Pool) Pop() *blockchain.Transaction { - if p.Len() > 0 { + if p.Size() > 0 { next := p.GetN(0) p.Delete(next) return next @@ -137,7 +135,7 @@ func (p *Pool) Pop() *blockchain.Transaction { // Peek returns the next transaction and does not remove it from the pool. func (p *Pool) Peek() *blockchain.Transaction { - if p.Len() > 0 { + if p.Size() > 0 { return p.GetN(0) } return nil @@ -167,7 +165,7 @@ func (p *Pool) NextBlock(chain *blockchain.BlockChain, // Try to grab as many transactions as the block will allow. // Test each transaction to see if we break size before adding. - for p.Len() > 0 { + for p.Size() > 0 { nextSize := p.Peek().Len() if b.Len()+nextSize < int(size) { b.Transactions = append(b.Transactions, p.Pop()) diff --git a/pool/pool_test.go b/pool/pool_test.go index f0d8d6f..b59ea5e 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -5,66 +5,52 @@ import ( "github.com/stretchr/testify/assert" "github.com/ubclaunchpad/cumulus/blockchain" + "github.com/ubclaunchpad/cumulus/consensus" ) func TestGetAndSetTransaction(t *testing.T) { p := New() bc, b := blockchain.NewValidTestChainAndBlock() - if p.Len() != 0 { - t.FailNow() - } - tr := b.Transactions[1] - if !p.Push(tr, bc) { - t.FailNow() - } + assert.Equal(t, p.Size(), 0) - if p.Len() != 1 { - t.FailNow() - } - - r := p.Get(tr.Input.Hash) - if r != tr { - t.FailNow() - } + tr := b.Transactions[1] + assert.Equal(t, p.Push(tr, bc), consensus.ValidTransaction) + assert.Equal(t, p.Size(), 1) + assert.Equal(t, tr, p.Get(blockchain.HashSum(tr))) p.Delete(tr) - if p.Len() != 0 { - t.FailNow() - } + assert.Equal(t, p.Size(), 0) } func TestSetBadTransaction(t *testing.T) { p := New() - bc := blockchain.NewTestBlockChain() - if p.Push(blockchain.NewTestTransaction(), bc) { - t.FailNow() - } + bc, _ := blockchain.NewValidTestChainAndBlock() + + // This transaction will have bad inputs. + txn := blockchain.NewTestTransaction() + code := p.Push(txn, bc) + assert.NotEqual(t, code, consensus.ValidTransaction) } func TestUpdatePool(t *testing.T) { p := New() bc, legitBlk := blockchain.NewValidTestChainAndBlock() badBlock := blockchain.NewTestBlock() - if p.Update(badBlock, bc) { - t.FailNow() - } + + // Make sure we cant update with a bad block. + assert.False(t, p.Update(badBlock, bc)) for _, tr := range legitBlk.Transactions[1:] { p.Push(tr, bc) } - if p.Len() == 0 { - t.FailNow() - } - if p.Len() != len(legitBlk.Transactions[1:]) { - t.FailNow() - } - if !p.Update(legitBlk, bc) { - t.FailNow() - } - if p.Len() != 0 { - t.FailNow() - } + // Assert transactions added. + assert.NotEqual(t, p.Size(), 0) + assert.Equal(t, p.Size(), len(legitBlk.Transactions[1:])) + + // Assert we can add update with a legit block and it drains pool. + assert.True(t, p.Update(legitBlk, bc)) + assert.Equal(t, p.Size(), 0) } func TestGetNewBlockEmpty(t *testing.T) { @@ -81,13 +67,10 @@ func TestGetIndex(t *testing.T) { for i := 0; i < numTxns; i++ { p.PushUnsafe(blockchain.NewTestTransaction()) } - if p.GetIndex(tr) != 0 { - t.FailNow() - } + assert.Equal(t, p.GetIndex(tr), 0) + for i := 0; i < numTxns; i++ { - if p.GetIndex(p.Order[i].Transaction) != i { - t.FailNow() - } + assert.Equal(t, p.GetIndex(p.Order[i].Transaction), i) } } @@ -101,12 +84,13 @@ func TestNextBlock(t *testing.T) { p.PushUnsafe(blockchain.NewTestTransaction()) } b := p.NextBlock(chain, blockchain.NewWallet().Public(), 1<<18) + assert.NotNil(t, b) assert.True(t, b.Len() < 1<<18) assert.True(t, b.Len() > 0) // The difference is off by one thanks to cloud transaction. - assert.Equal(t, len(b.Transactions), numTxns-p.Len()+1) + assert.Equal(t, len(b.Transactions), numTxns-p.Size()+1) assert.Equal(t, blockchain.HashSum(lastBlk), b.LastBlock) assert.Equal(t, uint64(0), b.Nonce) assert.Equal(t, uint32(nBlks), b.BlockNumber) @@ -125,10 +109,8 @@ func TestPop(t *testing.T) { func TestSetDedupes(t *testing.T) { p := New() t1 := blockchain.NewTestTransaction() - t2 := blockchain.NewTestTransaction() - t1.Input.Hash = t2.Input.Hash p.PushUnsafe(t1) - p.PushUnsafe(t2) - assert.Equal(t, p.Peek(), t2) - assert.Equal(t, p.Len(), 1) + p.PushUnsafe(t1) + assert.Equal(t, p.Size(), 1) + assert.Equal(t, p.Peek(), t1) }