Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/transaction-pooling' into 25-min…
Browse files Browse the repository at this point in the history
…ing-proof-of-work
  • Loading branch information
David Julien authored and David Julien committed Jun 20, 2017
2 parents 8446f1b + 4148a01 commit ef80df5
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 55 deletions.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.PHONY: test deps clean install_glide

PACKAGES = `go list ./... | grep -v vendor/`

all: cumulus
Expand All @@ -6,7 +8,7 @@ cumulus:
go build

test:
go test $(PACKAGES)
go test $(PACKAGES) --cover

deps:
glide install
Expand Down
2 changes: 1 addition & 1 deletion blockchain/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func NewInputBlock(t []*Transaction) *Block {
}
}

// NewInputBlock produces new block with given transactions and given input
// NewOutputBlock produces new block with given transactions and given input
// block.
func NewOutputBlock(t []*Transaction, input *Block) *Block {
return &Block{
Expand Down
2 changes: 1 addition & 1 deletion blockchain/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (bc *BlockChain) ValidTransaction(t *Transaction) (bool, TransactionCode) {
func (bc *BlockChain) ValidBlock(b *Block) (bool, BlockCode) {
// Check that block number between 0 and max blocks.
ix := b.BlockNumber - 1
if int(ix) > len(bc.Blocks)-1 || ix <= 0 {
if int(ix) > len(bc.Blocks)-1 || ix < 0 {
return false, BadBlockNumber
}

Expand Down
14 changes: 14 additions & 0 deletions blockchain/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,17 @@ func TestBlockDoubleSpend(t *testing.T) {
t.Fail()
}
}

func TestValidBlockBigNumber(t *testing.T) {
bc, b := NewValidChainAndBlock()
b.BlockNumber = uint32(len(bc.Blocks)) + 1

valid, code := bc.ValidBlock(b)

if valid {
t.Fail()
}
if code != BadBlockNumber {
t.Fail()
}
}
14 changes: 9 additions & 5 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ import:
- package: github.com/Sirupsen/logrus
- package: github.com/spf13/cobra
- package: github.com/spf13/pflag
- package: github.com/cevaris/ordered_map
- package: github.com/onsi/ginkgo
version: ^1.3.1
subpackages:
- ginkgo
- package: github.com/onsi/gomega
version: ^1.1.0
99 changes: 80 additions & 19 deletions pool/pool.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,101 @@
package pool

import (
"errors"
"time"

"github.com/cevaris/ordered_map"
"github.com/ubclaunchpad/cumulus/blockchain"
)

// PooledTransaction is a Transaction with a timetamp.
type PooledTransaction struct {
Transaction *blockchain.Transaction
Time time.Time
}

// Pool is a set of valid Transactions.
type Pool struct {
Transactions ordered_map.OrderedMap
Order []*PooledTransaction
ValidTransactions map[blockchain.Hash]*PooledTransaction
}

// New initializes a new pool.
func New() *Pool {
return &Pool{
Transactions: *ordered_map.NewOrderedMap(),
Order: []*PooledTransaction{},
ValidTransactions: map[blockchain.Hash]*PooledTransaction{},
}
}

// Len returns the number of transactions in the Pool.
func (p *Pool) Len() int {
return p.Transactions.Len()
return len(p.ValidTransactions)
}

// Get returns the tranasction with input transaction Hash h.
func (p *Pool) Get(h blockchain.Hash) *blockchain.Transaction {
return p.ValidTransactions[h].Transaction
}

// GetN returns the Nth transaction in the pool.
func (p *Pool) GetN(N int) *blockchain.Transaction {
return p.Order[N].Transaction
}

// Get returns the transction with input transaction Hash h.
func (p *Pool) Get(h blockchain.Hash) (interface{}, bool) {
return p.Transactions.Get(h)
// 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)
}

// getIndex does a binary search for a PooledTransaction by timestamp.
func getIndex(a []*PooledTransaction, target time.Time, low, high int) int {
mid := (low + high) / 2
if a[mid].Time == target {
return mid
} else if target.Before(a[mid].Time) {
return getIndex(a, target, low, mid-1)
} else {
return getIndex(a, target, mid+1, high)
}
}

// Set inserts a transaction into the pool, returning
// true if the Transaction was inserted (was valid).
func (p *Pool) Set(t *blockchain.Transaction, bc *blockchain.BlockChain) bool {
ok, _ := bc.ValidTransaction(t)
if ok {
p.Transactions.Set(t.Input.Hash, t)
if ok, _ := bc.ValidTransaction(t); ok {
p.set(t)
return true
}
return ok
return false
}

// SetUnsafe adds a transaction to the pool without validation.
func (p *Pool) SetUnsafe(t *blockchain.Transaction) {
p.set(t)
}

// Silently adds a transaction to the pool.
func (p *Pool) set(t *blockchain.Transaction) {
vt := &PooledTransaction{
Transaction: t,
Time: time.Now(),
}
p.Order = append(p.Order, vt)
p.ValidTransactions[t.Input.Hash] = vt
}

// Delete removes a transaction from the Pool.
func (p *Pool) Delete(t *blockchain.Transaction) {
p.Transactions.Delete(t.Input.Hash)
vt, ok := p.ValidTransactions[t.Input.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)
}
}

// Update updates the Pool by removing the Transactions found in the
// Block. If the Block is found invalid, then false is returned and no
// Block. If the Block is found invalid wrt bc, then false is returned and no
// Transactions are removed from the Pool.
func (p *Pool) Update(b *blockchain.Block, bc *blockchain.BlockChain) bool {
if ok, _ := bc.ValidBlock(b); !ok {
Expand All @@ -57,9 +107,20 @@ func (p *Pool) Update(b *blockchain.Block, bc *blockchain.BlockChain) bool {
return true
}

// GetBlock returns a new Block from the highest priority Transactions in
// the Pool, as well as a error indicating whether there were any
// Transactions to create a Block.
func (p *Pool) GetBlock() (*blockchain.Block, error) {
return blockchain.NewBlock(), errors.New("no transactions in pool")
// PopTxns returns the the largest of l or size of pool transactions.
// It selects the highest priority transactions, and removes them from the pool.
func (p *Pool) PopTxns(l int) []*blockchain.Transaction {
if p.Len() == 0 {
return make([]*blockchain.Transaction, 0)
}
if p.Len() < l {
l = p.Len()
}
txns := make([]*blockchain.Transaction, l)
for i := 0; i < l; i++ {
t := p.GetN(i)
txns[i] = t
p.Delete(t)
}
return txns
}
84 changes: 57 additions & 27 deletions pool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,33 @@ func TestGetAndSetTransaction(t *testing.T) {
p := New()
bc, b := blockchain.NewValidChainAndBlock()
if p.Len() != 0 {
t.Fail()
t.FailNow()
}
tr := b.Transactions[0]
if !p.Set(tr, bc) {
t.Fail()
t.FailNow()
}

if p.Len() != 1 {
t.Fail()
t.FailNow()
}

r, ok := p.Get(tr.Input.Hash)
if !ok {
t.Fail()
}
r := p.Get(tr.Input.Hash)
if r != tr {
t.Fail()
t.FailNow()
}

p.Delete(tr)
if p.Len() != 0 {
t.Fail()
t.FailNow()
}
}

func TestSetBadTransaction(t *testing.T) {
p := New()
bc := blockchain.NewBlockChain()
if p.Set(blockchain.NewTransaction(), bc) {
t.FailNow()
}
}

Expand All @@ -40,44 +45,69 @@ func TestUpdatePool(t *testing.T) {
bc, legitBlk := blockchain.NewValidChainAndBlock()
badBlock := blockchain.NewBlock()
if p.Update(badBlock, bc) {
t.Fail()
t.FailNow()
}

for _, tr := range legitBlk.Transactions {
p.Set(tr, bc)
if _, ok := p.Get(tr.Input.Hash); !ok {
t.Fail()
}
}
if p.Len() == 0 {
t.Fail()
t.FailNow()
}
if p.Len() != len(legitBlk.Transactions) {
t.Fail()
t.FailNow()
}

if !p.Update(legitBlk, bc) {
t.Fail()
t.FailNow()
}
if p.Len() != 0 {
t.Fail()
t.FailNow()
}
}

func TestGetNewBlock(t *testing.T) {
b := blockchain.NewBlock()
bc := blockchain.NewBlockChain()
func TestGetTxns(t *testing.T) {
p := New()
bc, b := blockchain.NewValidChainAndBlock()
for _, tr := range b.Transactions {
p.Set(tr, bc)
if !p.Set(tr, bc) {
t.FailNow()
}
}
nTxns := len(b.Transactions) + 12 // arbitrary.
txns := p.PopTxns(nTxns)
for _, tr := range txns {
if ok, _ := b.ContainsTransaction(tr); !ok {
t.FailNow()
}
}
if p.Len() != 0 {
t.FailNow()
}
}

func TestGetNewBlockEmpty(t *testing.T) {
p := New()
txns := p.PopTxns(305)
if len(txns) != 0 {
t.FailNow()
}
}

func TestGetIndex(t *testing.T) {
p := New()
numTxns := 1000
tr := blockchain.NewTransaction()
p.SetUnsafe(tr)
for i := 0; i < numTxns; i++ {
p.SetUnsafe(blockchain.NewTransaction())
}
newBlk, err := p.GetBlock()
if err != nil {
t.Fail()
if p.GetIndex(tr) != 0 {
t.FailNow()
}
for _, tr := range newBlk.Transactions {
if exists, _ := b.ContainsTransaction(tr); exists {
t.Fail()
for i := 0; i < numTxns; i++ {
if p.GetIndex(p.Order[i].Transaction) != i {
t.FailNow()
}
}
}
13 changes: 13 additions & 0 deletions test/cumulus_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestCumulus(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Cumulus Suite")
}
Loading

0 comments on commit ef80df5

Please sign in to comment.