From 049a2efcfa82478661fb35d59e0573b2fd57266b Mon Sep 17 00:00:00 2001 From: chadlagore Date: Sat, 12 Aug 2017 22:23:28 -0700 Subject: [PATCH 01/20] pluralized Inputs --- app/user.go | 2 +- blockchain/blockchain.go | 60 ++++++++++++++++++++++++++++++----- blockchain/genesis.go | 13 ++++---- blockchain/test_utils.go | 63 +++++++++++++++++++++++-------------- blockchain/transaction.go | 47 +++++++++++++++++++++++++-- blockchain/wallet.go | 4 +-- consensus/consensus.go | 30 +++++++++--------- consensus/consensus_test.go | 17 +++++----- consensus/errors.go | 4 +-- glide.lock | 6 ++-- glide.yaml | 1 + miner/miner.go | 6 ++-- pool/pool.go | 18 ++++++----- 13 files changed, 187 insertions(+), 84 deletions(-) diff --git a/app/user.go b/app/user.go index 363be80..dae5328 100644 --- a/app/user.go +++ b/app/user.go @@ -40,7 +40,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, diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 7fe4006..d09c050 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -4,6 +4,8 @@ import ( "encoding/gob" "errors" "io" + + intersect "github.com/juliangruber/go-intersect" ) // BlockChain represents a linked list of blocks @@ -45,17 +47,27 @@ func (bc *BlockChain) AppendBlock(b *Block) { bc.Blocks = append(bc.Blocks, b) } -// 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. +func (bc *BlockChain) GetAllInputs(t *Transaction) []*Transaction { + txns := make([]*Transaction, len(t.Inputs)) + for _, tx := range t.Inputs { + txns = append(txns, bc.GetInputTransaction(&tx)) + } + return txns } // ContainsTransaction returns true if the BlockChain contains the transaction @@ -69,7 +81,7 @@ func (bc *BlockChain) ContainsTransaction(t *Transaction, start, stop uint32) (b return false, 0, 0 } -// CopyLocalBlockByIndex returns a copy of a block in the local chain by index. +// CopyBlockByIndex returns a copy of a block in the local chain by index. func (bc *BlockChain) CopyBlockByIndex(i uint32) (*Block, error) { if i >= 0 && i < uint32(len(bc.Blocks)) { blk := bc.Blocks[i] @@ -80,3 +92,35 @@ func (bc *BlockChain) CopyBlockByIndex(i uint32) (*Block, error) { } return nil, errors.New("block request out of bounds") } + +// InputsSpentElsewhere returns true if inputs perported to be only spent +// on transaction t have been spent elsewhere after block index `start`. +func (bc *BlockChain) InputsSpentElsewhere(t *Transaction, start uint32) bool { + // This implementation runs in O(n * m * l * x) + // where n = number of blocks in range. + // m = number of transactions per block. + // l = number of inputs in a transaction. + // x = a factor of the hash efficiency function on (1, 2). + for _, b := range bc.Blocks[start:] { + for _, txn := range b.Transactions { + if HashSum(txn) == HashSum(t) { + continue + } else { + if t.InputsIntersect(txn) { + return true + } + } + } + } + return false +} + +// InputsIntersect returns true if the inputs of t intersect with those of +// other. +func (t *Transaction) InputsIntersect(other *Transaction) bool { + intersection := intersect.Hash(t.Inputs, other.Inputs) + if len(intersection.([]TxHashPointer)) > 0 { + return true + } + return false +} 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..4d2f040 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,46 +127,53 @@ 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. +// NewValidBlockChainFixture creates a valid blockchain of two blocks +// and returns the recipient of the only transaction in block 1. func NewValidBlockChainFixture() (*BlockChain, Wallet) { original := NewWallet() sender := NewWallet() recipient := NewWallet() - trA, _ := NewTestTransactionValue(original, sender, 2, 1) + // Transaction A is in block 0 at index 0 (sender awarded 2 coins). + trA, _ := NewTestTransactionValue(original, sender, 2, 1, 0) trA.Outputs = append(trA.Outputs, TxOutput{ Amount: 2, Recipient: sender.Public().Repr(), }) - trB, _ := NewTestTransactionValue(sender, recipient, 4, 1) - trB.Input.Hash = HashSum(trA) + // Transaction B is in block 1 at index 0 (sender sends 2 coins to recipient). + trB, _ := NewTestTransactionValue(sender, recipient, 2, 1, 1) + trB.Inputs[1].Hash = HashSum(trA) trB, _ = trB.TxBody.Sign(*sender, crand.Reader) + // CloudBase will bump our transactions forward. cbA, _ := NewValidCloudBaseTestTransaction() cbB, _ := NewValidCloudBaseTestTransaction() inputTransactions := []*Transaction{cbA, trA} outputTransactions := []*Transaction{cbB, trB} + // Create the blocks in the blockchain. inputBlock := NewTestInputBlock(inputTransactions) outputBlock := NewTestOutputBlock(outputTransactions, inputBlock) @@ -172,24 +183,28 @@ func NewValidBlockChainFixture() (*BlockChain, Wallet) { }, *recipient } -// NewValidTestChainAndBlock creates a valid BlockChain and a Block that is valid -// with respect to the BlockChain. +// NewValidTestChainAndBlock creates a valid BlockChain of 2 blocks, +// and a Block that is valid with respect to the BlockChain. +// [ 2in | 2out ] ->> [ 2in | 2out ] , [ 2in | 2out ] func NewValidTestChainAndBlock() (*BlockChain, *Block) { bc, s := NewValidBlockChainFixture() inputBlock := bc.Blocks[1] - inputTransaction := inputBlock.Transactions[0] - a := inputTransaction.Outputs[0].Amount + + // Collect the transaction following the CloudBase. + inputTransaction := inputBlock.Transactions[1] + a := inputTransaction.Outputs[1].Amount // Create a legit block that does *not* appear in bc. tbody := TxBody{ - Sender: s.Public(), - Input: TxHashPointer{ - BlockNumber: 1, - Hash: HashSum(inputTransaction), - Index: 0, - }, + Sender: s.Public(), + Inputs: make([]TxHashPointer, 1), Outputs: make([]TxOutput, 1), } + tbody.Inputs[0] = TxHashPointer{ + BlockNumber: 1, + Hash: HashSum(inputTransaction), + Index: 1, + } tbody.Outputs[0] = TxOutput{ Amount: a, Recipient: NewWallet().Public().Repr(), @@ -227,7 +242,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..470be17 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -3,6 +3,7 @@ package blockchain import ( "encoding/binary" "io" + "math" "github.com/ubclaunchpad/cumulus/common/util" ) @@ -40,7 +41,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 +54,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 +115,43 @@ 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 { + result := uint64(0) + // This is a bit crazy; filter all input transactions + // by this senders address and sum the outputs. + for _, in := range bc.GetAllInputs(t) { + result += in.GetTotalOutputFor(t.Sender.Repr()) + } + return result +} + +// 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(math.MaxUint32) // Why I have to cast this? No idea. + for _, in := range t.Inputs { + if in.BlockNumber < min { + min = in.BlockNumber + } + if in.BlockNumber > max { + max = in.BlockNumber + } + } + return min, max +} 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 70fec24..3e5e27b 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -19,26 +19,25 @@ 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 := bc.GetAllInputs(t) + if len(inputs) == 0 { + return false, NoInputTransactions } // Check that output to sender in input is equal to outputs in t - if !input.InputsEqualOutputs(t) { + if !(t.GetTotalOutput() == t.GetTotalInput(bc)) { 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 bc.InputsSpentElsewhere(t, start) { return false, Respend } @@ -60,10 +59,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 } @@ -223,9 +222,8 @@ func VerifyBlock(bc *blockchain.BlockChain, // 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) { + if i != j { + if trA.InputsIntersect(trB) { return false, DoubleSpend } } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index dd6de63..62614be 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -28,12 +28,9 @@ func TestVerifyTransactionNilTransaction(t *testing.T) { } func TestVerifyTransactionNoInputTransaction(t *testing.T) { - tr, _ := blockchain.NewTestTransactionValue( - blockchain.NewWallet(), - blockchain.NewWallet(), - 1, - 0, - ) + s := blockchain.NewWallet() + r := blockchain.NewWallet() + tr, _ := blockchain.NewTestTransactionValue(s, r, 1, 0, 0) bc, _ := blockchain.NewValidBlockChainFixture() valid, code := VerifyTransaction(bc, tr) @@ -41,7 +38,7 @@ func TestVerifyTransactionNoInputTransaction(t *testing.T) { if valid { t.Fail() } - if code != NoInputTransaction { + if code != NoInputTransactions { t.Fail() } } @@ -361,7 +358,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 { @@ -373,7 +370,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 { @@ -385,7 +382,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 e473578..4bf17ce 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 14d7695aad2f66fada940b379240f0bd400acb153b54600074e634b009f5b271 -updated: 2017-07-23T17:39:19.702683559-07:00 +hash: 1954740399ad628bee9d88f3092c1c51283b8aa55e281f413d031f03fb734703 +updated: 2017-08-12T21:45:36.257782645-07:00 imports: - name: github.com/abiosoft/ishell version: a24a06e34a7a9d8f894fb339b3e77333e4fc75ac @@ -26,6 +26,8 @@ imports: - json/token - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/juliangruber/go-intersect + version: b60b3a5b35ade098834e63cf9e094108650641a2 - name: github.com/magiconair/properties version: 51463bfca2576e06c62a8504b5c0f06d61312647 - name: github.com/mattn/go-colorable diff --git a/glide.yaml b/glide.yaml index e00fb91..7bf55c9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -19,3 +19,4 @@ import: - package: github.com/abiosoft/ishell - package: gopkg.in/kyokomi/emoji.v1 version: ^1.5.0 +- package: github.com/juliangruber/go-intersect diff --git a/miner/miner.go b/miner/miner.go index 7d94401..c33c666 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -108,7 +108,7 @@ func CloudBase( } cbTxBody := blockchain.TxBody{ Sender: blockchain.NilAddr, - Input: cbInput, + Inputs: []blockchain.TxHashPointer{cbInput}, Outputs: []blockchain.TxOutput{cbReward}, } cbTx := blockchain.Transaction{ @@ -121,8 +121,8 @@ func CloudBase( // 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++ + if tx.Inputs[0].BlockNumber == uint32(len(bc.Blocks)) { + tx.Inputs[0].Index++ } } diff --git a/pool/pool.go b/pool/pool.go index 0a53726..44b5bb6 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -11,7 +11,7 @@ import ( "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 @@ -53,7 +53,8 @@ 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 + hash := blockchain.HashSum(t) + target := p.ValidTransactions[hash].Time return getIndex(p.Order, target, 0, p.Len()-1) } @@ -88,10 +89,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 +100,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) } } From bca3b8cfccbad437e6e7a983d6800c9d001c0237 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Sat, 12 Aug 2017 22:33:23 -0700 Subject: [PATCH 02/20] move intersection function into transaction.go --- blockchain/blockchain.go | 12 ------------ blockchain/transaction.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index d09c050..c2aff31 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -4,8 +4,6 @@ import ( "encoding/gob" "errors" "io" - - intersect "github.com/juliangruber/go-intersect" ) // BlockChain represents a linked list of blocks @@ -114,13 +112,3 @@ func (bc *BlockChain) InputsSpentElsewhere(t *Transaction, start uint32) bool { } return false } - -// InputsIntersect returns true if the inputs of t intersect with those of -// other. -func (t *Transaction) InputsIntersect(other *Transaction) bool { - intersection := intersect.Hash(t.Inputs, other.Inputs) - if len(intersection.([]TxHashPointer)) > 0 { - return true - } - return false -} diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 470be17..92596ba 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -5,6 +5,7 @@ import ( "io" "math" + intersect "github.com/juliangruber/go-intersect" "github.com/ubclaunchpad/cumulus/common/util" ) @@ -155,3 +156,13 @@ func (bc *BlockChain) GetBlockRange(t *Transaction) (uint32, uint32) { } return min, max } + +// InputsIntersect returns true if the inputs of t intersect with those of +// other. +func (t *Transaction) InputsIntersect(other *Transaction) bool { + intersection := intersect.Hash(t.Inputs, other.Inputs) + if len(intersection.([]TxHashPointer)) > 0 { + return true + } + return false +} From 44ee8f2b25da71e58a0daff71b366744355cb885 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 20:08:39 -0700 Subject: [PATCH 03/20] Tests passing in blockchain package --- blockchain/blockchain.go | 24 +----------------- blockchain/test_utils.go | 11 ++++++-- blockchain/transaction.go | 46 ++++++++++++++++++++++++++++------ blockchain/transaction_test.go | 23 +++++++++++++++-- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index c2aff31..499f932 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -61,7 +61,7 @@ func (bc *BlockChain) GetInputTransaction(t *TxHashPointer) *Transaction { // GetAllInputs returns all the transactions referenced by a transaction // as inputs. func (bc *BlockChain) GetAllInputs(t *Transaction) []*Transaction { - txns := make([]*Transaction, len(t.Inputs)) + txns := []*Transaction{} for _, tx := range t.Inputs { txns = append(txns, bc.GetInputTransaction(&tx)) } @@ -90,25 +90,3 @@ func (bc *BlockChain) CopyBlockByIndex(i uint32) (*Block, error) { } return nil, errors.New("block request out of bounds") } - -// InputsSpentElsewhere returns true if inputs perported to be only spent -// on transaction t have been spent elsewhere after block index `start`. -func (bc *BlockChain) InputsSpentElsewhere(t *Transaction, start uint32) bool { - // This implementation runs in O(n * m * l * x) - // where n = number of blocks in range. - // m = number of transactions per block. - // l = number of inputs in a transaction. - // x = a factor of the hash efficiency function on (1, 2). - for _, b := range bc.Blocks[start:] { - for _, txn := range b.Transactions { - if HashSum(txn) == HashSum(t) { - continue - } else { - if t.InputsIntersect(txn) { - return true - } - } - } - } - return false -} diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index 4d2f040..0dc1d42 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -163,7 +163,7 @@ func NewValidBlockChainFixture() (*BlockChain, Wallet) { // Transaction B is in block 1 at index 0 (sender sends 2 coins to recipient). trB, _ := NewTestTransactionValue(sender, recipient, 2, 1, 1) - trB.Inputs[1].Hash = HashSum(trA) + trB.Inputs[0].Hash = HashSum(trA) trB, _ = trB.TxBody.Sign(*sender, crand.Reader) @@ -192,7 +192,7 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { // Collect the transaction following the CloudBase. inputTransaction := inputBlock.Transactions[1] - a := inputTransaction.Outputs[1].Amount + a := inputTransaction.Outputs[0].Amount // Create a legit block that does *not* appear in bc. tbody := TxBody{ @@ -216,6 +216,13 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { return bc, newBlock } +// NewValidTestChainAndTxn creates a valid BlockChain of 2 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 // max and min difficulties func NewValidTestTarget() Hash { diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 92596ba..02edf66 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -5,8 +5,8 @@ import ( "io" "math" - intersect "github.com/juliangruber/go-intersect" "github.com/ubclaunchpad/cumulus/common/util" + "gopkg.in/fatih/set.v0" ) // TxHashPointer is a reference to a transaction on the blockchain. @@ -135,7 +135,8 @@ func (t *Transaction) GetTotalInput(bc *BlockChain) uint64 { result := uint64(0) // This is a bit crazy; filter all input transactions // by this senders address and sum the outputs. - for _, in := range bc.GetAllInputs(t) { + inputs := bc.GetAllInputs(t) + for _, in := range inputs { result += in.GetTotalOutputFor(t.Sender.Repr()) } return result @@ -157,12 +158,43 @@ func (bc *BlockChain) GetBlockRange(t *Transaction) (uint32, uint32) { return min, max } -// InputsIntersect returns true if the inputs of t intersect with those of -// other. +// InputsIntersect returns true if the inputs of t intersect with those of other. func (t *Transaction) InputsIntersect(other *Transaction) bool { - intersection := intersect.Hash(t.Inputs, other.Inputs) - if len(intersection.([]TxHashPointer)) > 0 { - return true + 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 perported to be only spent +// on transaction t have been spent elsewhere after block index `start`. +func (t *Transaction) InputsSpentElsewhere(bc *BlockChain, start uint32) bool { + // This implementation runs in O(n * m * l * x) + // where n = number of blocks in range. + // m = number of transactions per block. + // l = number of inputs in a transaction. + // x = a factor of the hash efficiency function on (1, 2). + for _, b := range bc.Blocks[start:] { + for _, txn := range b.Transactions { + if HashSum(txn) == HashSum(t) { + continue + } else { + if t.InputsIntersect(txn) { + return true + } + } + } } return false } diff --git a/blockchain/transaction_test.go b/blockchain/transaction_test.go index b9d1d53..8396829 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,22 @@ 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)) +} From 64dbf9661c70fc2c8a6a568d0175ba01e1d9d7a1 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 20:09:10 -0700 Subject: [PATCH 04/20] Include set package --- glide.lock | 8 ++++---- glide.yaml | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/glide.lock b/glide.lock index 4bf17ce..da6c286 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 1954740399ad628bee9d88f3092c1c51283b8aa55e281f413d031f03fb734703 -updated: 2017-08-12T21:45:36.257782645-07:00 +hash: 590a23b6a165cc5f74245c58f6a0e0f4810c6ac08caafc10e56f37fe3c8f36d1 +updated: 2017-08-13T11:26:08.782717055-07:00 imports: - name: github.com/abiosoft/ishell version: a24a06e34a7a9d8f894fb339b3e77333e4fc75ac @@ -26,8 +26,6 @@ imports: - json/token - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/juliangruber/go-intersect - version: b60b3a5b35ade098834e63cf9e094108650641a2 - name: github.com/magiconair/properties version: 51463bfca2576e06c62a8504b5c0f06d61312647 - name: github.com/mattn/go-colorable @@ -75,6 +73,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 7bf55c9..ac09a86 100644 --- a/glide.yaml +++ b/glide.yaml @@ -19,4 +19,5 @@ import: - package: github.com/abiosoft/ishell - package: gopkg.in/kyokomi/emoji.v1 version: ^1.5.0 -- package: github.com/juliangruber/go-intersect +- package: gopkg.in/fatih/set.v0 + version: ^0.1.0 From 69d1c15c2f7581aedcffb92edffb3d4fa532b16d Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 21:41:46 -0700 Subject: [PATCH 05/20] Rebuild test fixtures :sweat_smile: --- blockchain/block.go | 2 +- blockchain/test_utils.go | 221 ++++++++++++++++++++++++++++++--------- 2 files changed, 170 insertions(+), 53 deletions(-) diff --git a/blockchain/block.go b/blockchain/block.go index 3d8bfd4..cf7389d 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -60,7 +60,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/test_utils.go b/blockchain/test_utils.go index 0dc1d42..e6907bd 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -147,76 +147,193 @@ func NewTestTransactionValue(s, r *Wallet, a uint64, i uint32, b uint32) (*Trans return tbody.Sign(*s, crand.Reader) } -// NewValidBlockChainFixture creates a valid blockchain of two blocks -// and returns the recipient of the only transaction in block 1. -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() - // Transaction A is in block 0 at index 0 (sender awarded 2 coins). - trA, _ := NewTestTransactionValue(original, sender, 2, 1, 0) - trA.Outputs = append(trA.Outputs, TxOutput{ - Amount: 2, - Recipient: sender.Public().Repr(), - }) + // Cloud base txns for our blocks. + cbA, _ := NewValidCloudBaseTestTransaction() + cbB, _ := NewValidCloudBaseTestTransaction() + cbC, _ := NewValidCloudBaseTestTransaction() - // Transaction B is in block 1 at index 0 (sender sends 2 coins to recipient). - trB, _ := NewTestTransactionValue(sender, recipient, 2, 1, 1) - trB.Inputs[0].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}, + } - // CloudBase will bump our transactions forward. - 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 recipientA). + 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 recipientA, some back to self. + 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}, + } + + // Sender has 1 coin left to send to bob. + tC, _ := TxBody{ + Sender: sender.Public(), + Inputs: []TxHashPointer{ + TxHashPointer{ + // Again look at block 1 (ignore CB) + BlockNumber: 1, + Index: 1, + Hash: HashSum(tB), + }, + }, + // One coin output to recipientB. + 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}, + } - // Create the blocks in the blockchain. - inputBlock := NewTestInputBlock(inputTransactions) - outputBlock := NewTestOutputBlock(outputTransactions, inputBlock) + 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 of 2 blocks, -// and a Block that is valid with respect to the BlockChain. -// [ 2in | 2out ] ->> [ 2in | 2out ] , [ 2in | 2out ] +// 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] + bc, wallets := NewValidBlockChainFixture() - // Collect the transaction following the CloudBase. - inputTransaction := inputBlock.Transactions[1] - a := inputTransaction.Outputs[0].Amount + // 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]), + }, + }, + // One output to bob. + Outputs: []TxOutput{ + TxOutput{ + Amount: 2, + Recipient: wallets["bob"].Public().Repr(), + }, + TxOutput{ + Amount: 1, + Recipient: wallets["alice"].Public().Repr(), + }, + }, + }.Sign(*wallets["alice"], crand.Reader) - // Create a legit block that does *not* appear in bc. - tbody := TxBody{ - Sender: s.Public(), - Inputs: make([]TxHashPointer, 1), - Outputs: make([]TxOutput, 1), - } - tbody.Inputs[0] = TxHashPointer{ - BlockNumber: 1, - Hash: HashSum(inputTransaction), - Index: 1, - } - tbody.Outputs[0] = TxOutput{ - Amount: a, - Recipient: NewWallet().Public().Repr(), - } + 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: 2, + LastBlock: HashSum(bc.Blocks[2]), + Target: NewValidTestTarget(), + Time: mrand.Uint32(), + Nonce: 0, + }, + Transactions: []*Transaction{cb, aliceToBob, bobToSender}, + } + + return bc, &blk } -// NewValidTestChainAndTxn creates a valid BlockChain of 2 blocks, +// 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() From 2e882e1e1ca0c1cf74446fd612f3754550b136f3 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 23:19:50 -0700 Subject: [PATCH 06/20] Added test for GetTotalOutputFor; adjusted InputsSpentElsewhere to handle multi-inputs --- blockchain/test_utils.go | 4 ++-- blockchain/transaction.go | 20 +++++++++++++++++--- blockchain/transaction_test.go | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index e6907bd..3e2b481 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -229,9 +229,9 @@ func NewValidBlockChainFixture() (*BlockChain, map[string]*Wallet) { Sender: sender.Public(), Inputs: []TxHashPointer{ TxHashPointer{ - // Again look at block 1 (ignore CB) + // Again look at block 1. BlockNumber: 1, - Index: 1, + Index: 1, // skip cb Hash: HashSum(tB), }, }, diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 02edf66..d8324d5 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -2,6 +2,7 @@ package blockchain import ( "encoding/binary" + "fmt" "io" "math" @@ -185,16 +186,29 @@ func (t *Transaction) InputsSpentElsewhere(bc *BlockChain, start uint32) bool { // m = number of transactions per block. // l = number of inputs in a transaction. // x = a factor of the hash efficiency function on (1, 2). + refs := set.New() for _, b := range bc.Blocks[start:] { for _, txn := range b.Transactions { if HashSum(txn) == HashSum(t) { continue } else { - if t.InputsIntersect(txn) { - return true - } + refs.Add(t.InputIntersection(txn)) } } } + + // refs represents all of the places in the BlockChain where + // the inputs to t were referenced. Transaction pointers in refs are + // problematic only if the output in the transaction pointed to + // contains t.Senders address. That means that t.Sender is trying to + // respend this same input. + fmt.Println(refs.Size()) + for _, ref := range refs.List() { + refTxn, _ := ref.(Transaction) + if refTxn.GetTotalOutputFor(t.Sender.Repr()) > 0 { + return true + } + } + return false } diff --git a/blockchain/transaction_test.go b/blockchain/transaction_test.go index 8396829..6b3a9e3 100644 --- a/blockchain/transaction_test.go +++ b/blockchain/transaction_test.go @@ -56,3 +56,17 @@ func TestInputIntersection(t *testing.T) { } 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)) +} From 54fda2f379d340fc98b9a323354b6852ff5974e3 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 23:25:28 -0700 Subject: [PATCH 07/20] Replace MaxUint32 with 0 --- blockchain/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain/transaction.go b/blockchain/transaction.go index d8324d5..4b04d70 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -147,7 +147,7 @@ func (t *Transaction) GetTotalInput(bc *BlockChain) uint64 { // to a transaction. func (bc *BlockChain) GetBlockRange(t *Transaction) (uint32, uint32) { min := uint32(math.MaxUint32) - max := uint32(math.MaxUint32) // Why I have to cast this? No idea. + max := uint32(0) for _, in := range t.Inputs { if in.BlockNumber < min { min = in.BlockNumber From 765ab7be325f4b18b5f7a66ee00e1312ecac002f Mon Sep 17 00:00:00 2001 From: chadlagore Date: Wed, 23 Aug 2017 23:44:59 -0700 Subject: [PATCH 08/20] Fix InputsSpentElsewhere; Respend test passing --- blockchain/transaction.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 4b04d70..76b8b4a 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -2,7 +2,6 @@ package blockchain import ( "encoding/binary" - "fmt" "io" "math" @@ -178,22 +177,16 @@ func (t *Transaction) InputSet() *set.Set { return set.New(a...) } -// InputsSpentElsewhere returns true if inputs perported to be only spent +// 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 { - // This implementation runs in O(n * m * l * x) - // where n = number of blocks in range. - // m = number of transactions per block. - // l = number of inputs in a transaction. - // x = a factor of the hash efficiency function on (1, 2). + // First build up a set of pointers to transactions on the BlockChain + // that share inputs with t. refs := set.New() for _, b := range bc.Blocks[start:] { for _, txn := range b.Transactions { - if HashSum(txn) == HashSum(t) { - continue - } else { - refs.Add(t.InputIntersection(txn)) - } + ixn := t.InputIntersection(txn) + refs.Merge(ixn) } } @@ -202,9 +195,9 @@ func (t *Transaction) InputsSpentElsewhere(bc *BlockChain, start uint32) bool { // problematic only if the output in the transaction pointed to // contains t.Senders address. That means that t.Sender is trying to // respend this same input. - fmt.Println(refs.Size()) for _, ref := range refs.List() { - refTxn, _ := ref.(Transaction) + ptr := ref.(TxHashPointer) + refTxn := bc.Blocks[ptr.BlockNumber].Transactions[ptr.Index] if refTxn.GetTotalOutputFor(t.Sender.Repr()) > 0 { return true } From efe53f6126796852a65a3dab8025a0195af5ec06 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 20:54:59 -0700 Subject: [PATCH 09/20] All transaction tests passing --- blockchain/test_utils.go | 2 +- blockchain/transaction.go | 31 ++++++------ consensus/consensus.go | 6 ++- consensus/consensus_test.go | 97 +++++++++++-------------------------- 4 files changed, 48 insertions(+), 88 deletions(-) diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index 3e2b481..2a8a788 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -285,7 +285,7 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { Hash: HashSum(bc.Blocks[1].Transactions[1]), }, }, - // One output to bob. + // One output to bob, one back to alice. Outputs: []TxOutput{ TxOutput{ Amount: 2, diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 76b8b4a..4f7b040 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -180,28 +180,25 @@ func (t *Transaction) InputSet() *set.Set { // 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 { - // First build up a set of pointers to transactions on the BlockChain - // that share inputs with t. - refs := set.New() + // 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 { - ixn := t.InputIntersection(txn) - refs.Merge(ixn) - } - } - // refs represents all of the places in the BlockChain where - // the inputs to t were referenced. Transaction pointers in refs are - // problematic only if the output in the transaction pointed to - // contains t.Senders address. That means that t.Sender is trying to - // respend this same input. - for _, ref := range refs.List() { - ptr := ref.(TxHashPointer) - refTxn := bc.Blocks[ptr.BlockNumber].Transactions[ptr.Index] - if refTxn.GetTotalOutputFor(t.Sender.Repr()) > 0 { - return true + // 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/consensus/consensus.go b/consensus/consensus.go index 3e5e27b..89208c1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -25,7 +25,9 @@ func VerifyTransaction(bc *blockchain.BlockChain, } // Check that output to sender in input is equal to outputs in t - if !(t.GetTotalOutput() == t.GetTotalInput(bc)) { + out := t.GetTotalOutput() + in := t.GetTotalInput(bc) + if out != in { return false, Overspend } @@ -37,7 +39,7 @@ func VerifyTransaction(bc *blockchain.BlockChain, // Test if inputs already spent elsewhere. start, _ := bc.GetBlockRange(t) - if bc.InputsSpentElsewhere(t, start) { + if t.InputsSpentElsewhere(bc, start) { return false, Respend } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 62614be..61b92c4 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,28 +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) { - s := blockchain.NewWallet() - r := blockchain.NewWallet() - tr, _ := blockchain.NewTestTransactionValue(s, r, 1, 0, 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 != NoInputTransactions { - t.Fail() - } + assert.False(t, valid) + assert.Equal(t, code, NoInputTransactions) } func TestVerifyTransactionOverspend(t *testing.T) { @@ -51,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 @@ -114,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) { @@ -129,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) { From 68e956ee7065675d89ccf2830757a548831b2e94 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 21:15:07 -0700 Subject: [PATCH 10/20] All tests pass for blocks --- blockchain/test_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index 2a8a788..a3f7a06 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -321,7 +321,7 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { blk := Block{ BlockHeader: BlockHeader{ - BlockNumber: 2, + BlockNumber: 3, LastBlock: HashSum(bc.Blocks[2]), Target: NewValidTestTarget(), Time: mrand.Uint32(), From 1d8a9c7ab085bc44772366ce138e9dba01fa22fe Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 21:15:39 -0700 Subject: [PATCH 11/20] Adjust last block check; add asserts --- consensus/consensus.go | 8 ++++---- consensus/consensus_test.go | 34 +++++++++++----------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 89208c1..929cb69 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -170,14 +170,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 } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 61b92c4..f5fd01b 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -112,28 +112,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) { @@ -142,12 +134,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) { From 2c244d5760511d887e88799e1a4a277bbb4c8909 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 21:45:32 -0700 Subject: [PATCH 12/20] Update test for DoubleSpend to allow for many-to-one reference inputs --- consensus/consensus.go | 29 ++++++++++++++++++++++------- consensus/consensus_test.go | 8 ++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 929cb69..9113e2e 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -4,6 +4,8 @@ import ( "crypto/ecdsa" "math" + "gopkg.in/fatih/set.v0" + log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/blockchain" c "github.com/ubclaunchpad/cumulus/common/constants" @@ -221,14 +223,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 { - if trA.InputsIntersect(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 f5fd01b..532c157 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -209,12 +209,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) { From b610da95ebf4f5f41b493031e68898cfe9556d0b Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 21:52:53 -0700 Subject: [PATCH 13/20] Replace Len method with Size --- pool/pool.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pool/pool.go b/pool/pool.go index 44b5bb6..f42536f 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -31,14 +31,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. @@ -55,7 +55,7 @@ func (p *Pool) GetN(N int) *blockchain.Transaction { func (p *Pool) GetIndex(t *blockchain.Transaction) int { hash := blockchain.HashSum(t) target := p.ValidTransactions[hash].Time - return getIndex(p.Order, target, 0, p.Len()-1) + return getIndex(p.Order, target, 0, p.Size()-1) } // getIndex does a binary search for a PooledTransaction by timestamp. @@ -129,7 +129,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 @@ -139,7 +139,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 @@ -169,7 +169,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()) From 735f0f96266516bd800029b150bd2aaba06ea9e1 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Thu, 24 Aug 2017 22:12:59 -0700 Subject: [PATCH 14/20] Reseting glide for merge --- glide.lock | 30 +++++++++++++----------------- glide.yaml | 8 -------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/glide.lock b/glide.lock index da6c286..0dd1512 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 590a23b6a165cc5f74245c58f6a0e0f4810c6ac08caafc10e56f37fe3c8f36d1 -updated: 2017-08-13T11:26:08.782717055-07:00 +hash: b8d3d8395d2867faced0c31743cd56384beb07b6290c0e339c84d2354de4fcbc +updated: 2017-07-29T17:35:29.15377758-07:00 imports: - name: github.com/abiosoft/ishell version: a24a06e34a7a9d8f894fb339b3e77333e4fc75ac @@ -27,7 +27,7 @@ imports: - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/magiconair/properties - version: 51463bfca2576e06c62a8504b5c0f06d61312647 + version: be5ece7dd465ab0765a9682137865547526d1dfb - name: github.com/mattn/go-colorable version: 5411d3eea5978e6cdc258b30de592b60df6aba96 repo: https://github.com/mattn/go-colorable @@ -36,16 +36,10 @@ imports: repo: https://github.com/mattn/go-isatty - name: github.com/mitchellh/mapstructure version: d0303fe809921458f417bcf828397a65db30a7e4 -- name: github.com/onsi/ginkgo - version: 9eda700730cba42af70d53180f9dcce9266bc2bc - subpackages: - - ginkgo -- name: github.com/onsi/gomega - version: c893efa28eb45626cdaa76c9f653b62488858837 - name: github.com/pelletier/go-toml version: 69d355db5304c0f7f809a2edc054553e7142f016 - name: github.com/Sirupsen/logrus - version: 7dd06bf38e1e13df288d471a57d5adbac106be9e + version: 181d419aa9e2223811b824e8f0b4af96f9ba9302 - name: github.com/spf13/afero version: 9be650865eab0c12963d8753212f4f9c66cdcf12 subpackages: @@ -53,32 +47,34 @@ imports: - name: github.com/spf13/cast version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 - name: github.com/spf13/cobra - version: 8c6fa02d2225de0f9bdcb7ca912556f68d172d8c + version: 34594c771f2c18301dc152640ad40ece28795373 - name: github.com/spf13/jwalterweatherman version: 0efa5202c04663c757d84f90f5219c1250baf94f - name: github.com/spf13/pflag version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 - name: github.com/spf13/viper - version: a1ecfa6a20bd4ef9e9caded262ee1b1b26847675 + version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 - name: github.com/stretchr/testify version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert +- name: golang.org/x/crypto + version: 558b6879de74bc843225cde5686419267ff707ca + subpackages: + - ssh/terminal - name: golang.org/x/sys - version: 4ed4d404df456f81e878683a0363ed3013a59003 + version: 0f826bdd13b500be0f1d4004938ad978fcc6031e subpackages: - unix - name: golang.org/x/text - version: 2bf8f2a19ec09c670e931282edfe6567f6be21c9 + version: a467ab3c3977c4a820c2a4a886d54eb7d68901f0 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 - version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b + version: 25c4ec802a7d637f88d584ab26798e94ad14c13b testImports: - name: github.com/davecgh/go-spew version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 diff --git a/glide.yaml b/glide.yaml index ac09a86..a5ed419 100644 --- a/glide.yaml +++ b/glide.yaml @@ -6,12 +6,6 @@ import: - package: github.com/spf13/cobra - package: github.com/spf13/viper - package: github.com/spf13/pflag -- package: github.com/onsi/ginkgo - version: ^1.3.1 - subpackages: - - ginkgo -- package: github.com/onsi/gomega - version: ^1.1.0 - package: github.com/stretchr/testify version: ~1.1.4 subpackages: @@ -19,5 +13,3 @@ 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 From 08dd36316121f0553fa5e193d72e425e13b3c2f3 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Fri, 25 Aug 2017 21:31:02 -0700 Subject: [PATCH 15/20] Pool updated to handle multi inputs; Handled panic where input index ref non-existent block --- blockchain/blockchain.go | 13 +++++-- blockchain/transaction.go | 10 +++-- consensus/consensus.go | 6 +-- pool/pool.go | 12 ++---- pool/pool_test.go | 80 +++++++++++++++------------------------ 5 files changed, 54 insertions(+), 67 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 9d71ccb..dea2e4e 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -67,13 +67,18 @@ func (bc *BlockChain) GetInputTransaction(t *TxHashPointer) *Transaction { } // GetAllInputs returns all the transactions referenced by a transaction -// as inputs. -func (bc *BlockChain) GetAllInputs(t *Transaction) []*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 { - txns = append(txns, bc.GetInputTransaction(&tx)) + nextTxn := bc.GetInputTransaction(&tx) + if nextTxn == nil { + return nil, errors.New("input transaction not found") + } + txns = append(txns, nextTxn) } - return txns + return txns, nil } // ContainsTransaction returns true if the BlockChain contains the transaction diff --git a/blockchain/transaction.go b/blockchain/transaction.go index 4f7b040..ca70982 100644 --- a/blockchain/transaction.go +++ b/blockchain/transaction.go @@ -131,15 +131,19 @@ func (t *Transaction) GetTotalOutputFor(recipient string) uint64 { // GetTotalInput sums the input amounts from the transaction. // Requires the blockchain for lookups. -func (t *Transaction) GetTotalInput(bc *BlockChain) uint64 { +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 := bc.GetAllInputs(t) + inputs, err := bc.GetAllInputs(t) + if err != nil { + return 0, err + } for _, in := range inputs { result += in.GetTotalOutputFor(t.Sender.Repr()) } - return result + return result, nil } // GetBlockRange returns the start and end block indexes for the inputs diff --git a/consensus/consensus.go b/consensus/consensus.go index 63640c2..52de19c 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -22,14 +22,14 @@ func VerifyTransaction(bc *blockchain.BlockChain, } // Find the transaction input in the chain (by hash) - inputs := bc.GetAllInputs(t) - if len(inputs) == 0 { + 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 out := t.GetTotalOutput() - in := t.GetTotalInput(bc) + in, err := t.GetTotalInput(bc) if out != in { return false, Overspend } diff --git a/pool/pool.go b/pool/pool.go index 97a4161..76e253c 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -3,8 +3,6 @@ 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" @@ -73,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. diff --git a/pool/pool_test.go b/pool/pool_test.go index f0d8d6f..91c3b78 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.ObjectsAreEqual(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) } From 40592cee4645ecde4b28f3ddf4061f71f4d5d039 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Fri, 25 Aug 2017 21:50:19 -0700 Subject: [PATCH 16/20] Fix up app tests to expect new fixture --- app/app.go | 8 ++++---- app/app_test.go | 39 ++++++++++++++++++++++++++++++--------- app/user.go | 5 ++++- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/app.go b/app/app.go index faff9be..de1a6b5 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()) @@ -351,7 +351,7 @@ func (a *App) RunMiner() { miningResult := miner.Mine(a.Chain, blockToMine) if miningResult.Complete { - log.Info("Successfully mined a block!") + // log.Debug("Successfully mined a block!") push := msg.Push{ ResourceType: msg.ResourceBlock, Resource: blockToMine, @@ -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..8ede1f8 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -47,7 +47,7 @@ func TestPushHandlerNewTestTransaction(t *testing.T) { select { case tr, ok := <-a.transactionQueue: assert.True(t, ok) - assert.Equal(t, tr, txn) + assert.ObjectsAreEqual(tr, txn) } } @@ -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.ObjectsAreEqual(blk, a.Chain.Blocks[2]) // 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 80a3909..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" @@ -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") } From a605059631f84e39c56362b8cfe29b55aae0370e Mon Sep 17 00:00:00 2001 From: chadlagore Date: Fri, 25 Aug 2017 21:54:30 -0700 Subject: [PATCH 17/20] Add set package --- glide.lock | 6 ++++-- glide.yaml | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) 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 From d246cb0430ed7eca21ca9396e10c179cf1208d63 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Fri, 25 Aug 2017 23:06:18 -0700 Subject: [PATCH 18/20] Comments --- blockchain/test_utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index a3f7a06..bcf76ad 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -188,7 +188,7 @@ func NewValidBlockChainFixture() (*BlockChain, map[string]*Wallet) { Transactions: []*Transaction{cbA, tA}, } - // Transaction B is at index 1 in block 1 (sender sends 3 coins to recipientA). + // Transaction B is at index 1 in block 1 (sender sends 3 coins to alice). tB, _ := TxBody{ Sender: sender.Public(), Inputs: []TxHashPointer{ @@ -199,7 +199,7 @@ func NewValidBlockChainFixture() (*BlockChain, map[string]*Wallet) { Hash: HashSum(tA), }, }, - // Send some outputs to recipientA, some back to self. + // Send some outputs to alice, some back to sender. Outputs: []TxOutput{ TxOutput{ Amount: 3, @@ -235,7 +235,7 @@ func NewValidBlockChainFixture() (*BlockChain, map[string]*Wallet) { Hash: HashSum(tB), }, }, - // One coin output to recipientB. + // One coin output to bob. Outputs: []TxOutput{ TxOutput{ Amount: 1, From fe4a0d6cbc77d2265c5deb7a50e16b01ab926166 Mon Sep 17 00:00:00 2001 From: chadlagore Date: Sat, 26 Aug 2017 14:30:33 -0700 Subject: [PATCH 19/20] Replace ObjectsAreEqual and fix debug noise (for real) --- app/app.go | 2 +- app/app_test.go | 6 +++--- pool/pool_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/app.go b/app/app.go index de1a6b5..b72e0aa 100644 --- a/app/app.go +++ b/app/app.go @@ -351,7 +351,7 @@ func (a *App) RunMiner() { miningResult := miner.Mine(a.Chain, blockToMine) if miningResult.Complete { - // log.Debug("Successfully mined a block!") + log.Info("Successfully mined a block!") push := msg.Push{ ResourceType: msg.ResourceBlock, Resource: blockToMine, diff --git a/app/app_test.go b/app/app_test.go index 8ede1f8..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) { @@ -47,7 +47,7 @@ func TestPushHandlerNewTestTransaction(t *testing.T) { select { case tr, ok := <-a.transactionQueue: assert.True(t, ok) - assert.ObjectsAreEqual(tr, txn) + assert.Equal(t, tr, txn) } } @@ -138,7 +138,7 @@ func TestHandleValidBlock(t *testing.T) { bc, blk := blockchain.NewValidTestChainAndBlock() a.Chain = bc a.HandleBlock(blk) - assert.ObjectsAreEqual(blk, a.Chain.Blocks[2]) + assert.Equal(t, blk, a.Chain.Blocks[3]) // TODO: Assert miner restarted. // TODO: Assert pool appropriately emptied. diff --git a/pool/pool_test.go b/pool/pool_test.go index 91c3b78..b59ea5e 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -16,7 +16,7 @@ func TestGetAndSetTransaction(t *testing.T) { tr := b.Transactions[1] assert.Equal(t, p.Push(tr, bc), consensus.ValidTransaction) assert.Equal(t, p.Size(), 1) - assert.ObjectsAreEqual(tr, p.Get(blockchain.HashSum(tr))) + assert.Equal(t, tr, p.Get(blockchain.HashSum(tr))) p.Delete(tr) assert.Equal(t, p.Size(), 0) From e24d34216c342551169e0cd200597c8920defd2e Mon Sep 17 00:00:00 2001 From: chadlagore Date: Sat, 26 Aug 2017 14:48:12 -0700 Subject: [PATCH 20/20] Remove index bump --- miner/miner.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index dbfd8d0..0cd152b 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -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.Inputs[0].BlockNumber == uint32(len(bc.Blocks)) { - tx.Inputs[0].Index++ - } - } - return b }