From 6e1c89bbc26114ce268b420339a70e48e6323a3e Mon Sep 17 00:00:00 2001 From: David Julien Date: Wed, 26 Jul 2017 16:22:37 -0400 Subject: [PATCH] Initial refactor, moving all validation into consensus --- blockchain/genesis_test.go | 7 +- blockchain/test_utils.go | 11 +- blockchain/validation.go | 307 ----------------- blockchain/validation_test.go | 594 -------------------------------- common/constants/big.go | 13 + consensus/consensus.go | 276 +++++++++++---- consensus/consensus_test.go | 632 ++++++++++++++++++++++++++++++---- consensus/current.go | 39 +++ consensus/errors.go | 106 ++++++ miner/miner.go | 10 +- miner/miner_test.go | 22 +- pool/pool.go | 5 +- 12 files changed, 939 insertions(+), 1083 deletions(-) delete mode 100644 blockchain/validation.go delete mode 100644 blockchain/validation_test.go create mode 100644 consensus/current.go create mode 100644 consensus/errors.go diff --git a/blockchain/genesis_test.go b/blockchain/genesis_test.go index fa8c518..ca5aa45 100644 --- a/blockchain/genesis_test.go +++ b/blockchain/genesis_test.go @@ -8,7 +8,7 @@ import ( func TestGenesis(t *testing.T) { miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) + currentTarget := BigIntToHash(c.MaxTarget) currentBlockReward := uint64(25) gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) @@ -31,9 +31,4 @@ func TestGenesis(t *testing.T) { if len(gb.Transactions) != 1 { t.Fail() } - - // Check if the transaction is a valid cloud base transaction - if valid, _ := ValidCloudBase(gb.Transactions[0]); !valid { - t.Fail() - } } diff --git a/blockchain/test_utils.go b/blockchain/test_utils.go index bbcced2..34d7c49 100644 --- a/blockchain/test_utils.go +++ b/blockchain/test_utils.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "math/big" mrand "math/rand" - "time" c "github.com/ubclaunchpad/cumulus/common/constants" "github.com/ubclaunchpad/cumulus/common/util" @@ -205,12 +204,12 @@ func NewValidTestChainAndBlock() (*BlockChain, *Block) { // NewValidTestTarget creates a new valid target that is a random value between the // max and min difficulties func NewValidTestTarget() Hash { - r := new(big.Int).Rand( - mrand.New(mrand.NewSource(time.Now().Unix())), - util.BigAdd(MaxTarget, c.Big1), + return BigIntToHash( + new(big.Int).Div( + c.MaxTarget, + c.MinTarget, + ), ) - r.Add(r, c.Big1) - return BigIntToHash(r) } // NewValidCloudBaseTestTransaction returns a new valid CloudBase transaction and diff --git a/blockchain/validation.go b/blockchain/validation.go deleted file mode 100644 index f94a722..0000000 --- a/blockchain/validation.go +++ /dev/null @@ -1,307 +0,0 @@ -package blockchain - -// ValidTransaction checks whether a transaction is valid, assuming the -import ( - "crypto/ecdsa" - - log "github.com/Sirupsen/logrus" - c "github.com/ubclaunchpad/cumulus/common/constants" - "github.com/ubclaunchpad/cumulus/common/util" -) - -// TransactionCode is returned from ValidTransaction. -type TransactionCode uint32 - -// CloudBaseTransactionCode is returned from ValidCloudBaseTransaction. -type CloudBaseTransactionCode uint32 - -// GenesisBlockCode is returned from ValidGenesisBlock. -type GenesisBlockCode uint32 - -// BlockCode is returned from ValidBlock. -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 - // Overspend is returned when transaction outputs exceed transaction inputs. - Overspend - // BadSig is returned when the signature verification fails. - BadSig - // Respend is returned when inputs have been spent elsewhere in the chain. - Respend - // NilTransaction is returned when the transaction pointer is nil. - NilTransaction -) - -const ( - // ValidCloudBaseTransaction is returned when a transaction is a valid - // CloudBase transaction. - ValidCloudBaseTransaction CloudBaseTransactionCode = iota - // BadCloudBaseSender is returned when the sender address in the CloudBase - // transaction is not a NilAddr. - BadCloudBaseSender - // BadCloudBaseInput is returned when all the fields inf the CloudBase - // transaction input are not equal to 0. - BadCloudBaseInput - // BadCloudBaseOutput is returned when the CloudBase transaction output is - // invalid. - BadCloudBaseOutput - // BadCloudBaseSig is returned when the CloudBase transaction signature is - // not equal to NilSig. - BadCloudBaseSig - // NilCloudBaseTransaction is returned when the CloudBase transaction - // pointer is nil - NilCloudBaseTransaction -) - -const ( - // ValidGenesisBlock is returned when a block is a valid genesis block. - ValidGenesisBlock GenesisBlockCode = iota - // BadGenesisLastBlock is returned when the LastBlock of the genesis block - // is not equal to 0. - BadGenesisLastBlock - // BadGenesisTransactions is returned when the genesis block does not contain - // exactly 1 transaction, the first CloudBase transaction. - BadGenesisTransactions - // BadGenesisCloudBaseTransaction is returned when the transaction in the - // genesis block is not a valid CloudBase transaction. - BadGenesisCloudBaseTransaction - // BadGenesisBlockNumber is returned when the block number in the genesis - // block is not equal to 0. - BadGenesisBlockNumber - // BadGenesisTarget is returned when the genesis block's target is invalid. - BadGenesisTarget - // BadGenesisTime is returned when the genesis block's time is invalid. - BadGenesisTime - // NilGenesisBlock is returned when the genesis block is equal to nil. - NilGenesisBlock -) - -const ( - // ValidBlock is returned when the block is valid. - ValidBlock BlockCode = iota - // BadTransaction is returned when the block contains an invalid - // transaction. - BadTransaction - // BadTime is returned when the block contains an invalid time. - BadTime - // BadTarget is returned when the block contains an invalid target. - BadTarget - // BadBlockNumber is returned when block number is not one greater than - // previous block. - BadBlockNumber - // BadHash is returned when the block contains incorrect hash. - BadHash - // DoubleSpend is returned when two transactions in the block share inputs, - // but outputs > inputs. - DoubleSpend - // BadCloudBaseTransaction is returned when a block does not have a - // CloudBase transaction as the first transaction in its list of - // transactions. - BadCloudBaseTransaction - // BadGenesisBlock is returned if the block is a genesis block and is - // invalid. - BadGenesisBlock - // NilBlock is returned when the block pointer is nil. - NilBlock -) - -var ( - // MaxTarget is the minimum difficulty - MaxTarget = util.BigSub(util.BigExp(2, 232), c.Big1) - // MinTarget is the maximum difficulty value - MinTarget = c.Big1 -) - -// ValidTransaction tests whether a transaction valid. -func (bc *BlockChain) ValidTransaction(t *Transaction) (bool, TransactionCode) { - - // Check if the transaction is equal to nil - if t == nil { - return false, NilTransaction - } - - // Find the transaction input in the chain (by hash) - var input *Transaction - input = bc.GetInputTransaction(t) - if input == nil || HashSum(input) != t.Input.Hash { - return false, NoInputTransaction - } - - // Check that output to sender in input is equal to outputs in t - if !input.InputsEqualOutputs(t) { - return false, Overspend - } - - // Verify signature of t - hash := 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 { - return false, Respend - } - - return true, ValidTransaction -} - -// ValidCloudBase returns true if a transaction is a valid CloudBase transaction -// and false otherwise -func ValidCloudBase(t *Transaction) (bool, CloudBaseTransactionCode) { - - // Check if the CloudBase transaction is equal to nil - if t == nil { - return false, NilCloudBaseTransaction - } - - // Check that the sender address is nil - if t.Sender != NilAddr { - return false, BadCloudBaseSender - } - - // Check that the input is 0 - if t.TxBody.Input.BlockNumber != 0 || - t.TxBody.Input.Hash != NilHash || - t.Input.Index != 0 { - return false, BadCloudBaseInput - } - - // Check that the output is set and that the reward is != 0 - if len(t.Outputs) == 0 || - len(t.Outputs) > 1 || - t.Outputs[0].Amount == 0 || - t.Outputs[0].Recipient == NilAddr { - return false, BadCloudBaseOutput - } - - // Asser that the signature is equal to nil - if t.Sig != NilSig { - return false, BadCloudBaseSig - } - - return true, ValidCloudBaseTransaction -} - -// ValidGenesisBlock checks whether a block is a valid genesis block. -func (bc *BlockChain) ValidGenesisBlock(gb *Block) (bool, GenesisBlockCode) { - - // Check if the genesis block is equal to nil. - if gb == nil { - return false, NilGenesisBlock - } - - // Check if the genesis block's block number is equal to 0. - if gb.BlockHeader.BlockNumber != 0 || - bc.Blocks[0] != gb { - return false, BadGenesisBlockNumber - } - - // Check if the genesis block's last block hash is equal to 0. - if HashToBigInt(gb.BlockHeader.LastBlock).Cmp(c.Big0) != 0 { - return false, BadGenesisLastBlock - } - - // Check if the size of the transaction list is equal to 1. - if len(gb.Transactions) != 1 { - return false, BadGenesisTransactions - } - - // Check if the transaction is a valid cloud base transaction. - if valid, code := ValidCloudBase(gb.Transactions[0]); !valid { - log.Errorf("Invalid CloudBase, CloudBaseTransactionCode: %d", code) - return false, BadGenesisCloudBaseTransaction - } - - // Check that the target is within the min and max difficulty levels. - target := HashToBigInt(gb.Target) - if target.Cmp(MaxTarget) == 1 || target.Cmp(MinTarget) == -1 { - return false, BadGenesisTarget - } - - // Check that time is not greater than current time or equal to 0. - if uint32(gb.Time) == 0 { - return false, BadGenesisTime - } - - return true, ValidGenesisBlock -} - -// ValidBlock checks whether a block is valid. -func (bc *BlockChain) ValidBlock(b *Block) (bool, BlockCode) { - - // Check if the block is equal to nil. - if b == nil { - return false, NilBlock - } - - // Check if the block is the genesis block. - if b.BlockHeader.BlockNumber == 0 || bc.Blocks[0] == b { - if valid, code := bc.ValidGenesisBlock(b); !valid { - log.Errorf("Invalid GenesisBlock, GenesisBlockCode: %d", code) - return false, BadGenesisBlock - } - return true, ValidBlock - } - - // Check that block number between 0 and max blocks. - ix := b.BlockNumber - 1 - if int(ix) > len(bc.Blocks)-1 || ix < 0 { - return false, BadBlockNumber - } - - // Check that block number is one greater than last block - lastBlock := bc.Blocks[ix] - if lastBlock.BlockNumber != ix { - return false, BadBlockNumber - } - - // Check that the first transaction is a CloudBase transaction - if valid, code := ValidCloudBase(b.Transactions[0]); !valid { - log.Errorf("Invalid CloudBase, CloudBaseTransactionCode: %d", code) - return false, BadCloudBaseTransaction - } - - // Verify every Transaction in the block. - for _, t := range b.Transactions[1:] { - if valid, _ := bc.ValidTransaction(t); !valid { - return false, BadTransaction - } - } - - // Check that the target is within the min and max difficulty levels - target := HashToBigInt(b.Target) - if target.Cmp(MaxTarget) == 1 || target.Cmp(MinTarget) == -1 { - return false, BadTarget - } - - // Check that time is not greater than current time or equal to 0 - if uint32(b.Time) == 0 { - return false, BadTime - } - - // Check that hash of last block is correct - if HashSum(lastBlock) != b.LastBlock { - return false, BadHash - } - - // 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 - } - } - } - } - - return true, ValidBlock -} diff --git a/blockchain/validation_test.go b/blockchain/validation_test.go deleted file mode 100644 index 3ffeb38..0000000 --- a/blockchain/validation_test.go +++ /dev/null @@ -1,594 +0,0 @@ -package blockchain - -import ( - "crypto/rand" - "fmt" - "testing" - - c "github.com/ubclaunchpad/cumulus/common/constants" - "github.com/ubclaunchpad/cumulus/common/util" -) - -func TestValidTransactionNilTransaction(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - - valid, code := bc.ValidTransaction(nil) - - if valid { - t.Fail() - } - if code != NilTransaction { - t.Fail() - } -} - -func TestValidTransactionNoInputTransaction(t *testing.T) { - tr, _ := NewTestTransactionValue(NewWallet(), NewWallet(), 1, 0) - bc, _ := NewValidBlockChainFixture() - - valid, code := bc.ValidTransaction(tr) - - if valid { - t.Fail() - } - if code != NoInputTransaction { - t.Fail() - } -} - -func TestValidTransactionOverspend(t *testing.T) { - // 2 + 2 = 5 ? - bc, _ := NewValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[1] - tr.Outputs[0].Amount = 5 - - valid, code := bc.ValidTransaction(tr) - - if valid { - t.Fail() - } - if code != Overspend { - fmt.Println(code) - t.Fail() - } -} - -func TestValidTransactionSignatureFail(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[1] - - fakeSender := NewWallet() - tr, _ = tr.TxBody.Sign(fakeSender, rand.Reader) - bc.Blocks[1].Transactions[1] = tr - - valid, code := bc.ValidTransaction(tr) - if valid { - t.Fail() - } - if code != BadSig { - t.Fail() - } -} - -func TestValidTransactionPass(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - tr := b.Transactions[1] - - valid, code := bc.ValidTransaction(tr) - - if !valid { - t.Fail() - } - if code != ValidTransaction { - t.Fail() - } -} - -func TestTransactionRespend(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - trC := bc.Blocks[1].Transactions[1] - b := NewTestOutputBlock([]*Transaction{trC}, bc.Blocks[1]) - bc.AppendBlock(b) - - valid, code := bc.ValidTransaction(trC) - - if valid { - t.Fail() - } - if code != Respend { - t.Fail() - } -} - -func TestValidBlockNilBlock(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - - valid, code := bc.ValidBlock(nil) - - if valid { - t.Fail() - } - if code != NilBlock { - t.Fail() - } -} - -func TestValidBlockBadGenesisBlock(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.Target = BigIntToHash(util.BigExp(2, 255)) - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisBlock { - t.Fail() - } -} - -func TestValidBlockBadTransaction(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - tr := bc.Blocks[1].Transactions[1] - tr.Outputs[0].Amount = 5 - - valid, code := bc.ValidBlock(bc.Blocks[1]) - - if valid { - t.Fail() - } - if code != BadTransaction { - t.Fail() - } -} - -func TestValidBlockBadBlockNumber(t *testing.T) { - bc, _ := NewValidBlockChainFixture() - bc.Blocks[1].BlockNumber = 2 - - valid, code := bc.ValidBlock(bc.Blocks[1]) - - if valid { - t.Fail() - } - if code != BadBlockNumber { - t.Fail() - } -} - -func TestValidBlockBadHash(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - b.BlockHeader.LastBlock = NewTestHash() - - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadHash { - t.Fail() - } -} - -func TestValidBlockBadTime(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - b.Time = 0 - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadTime { - t.Fail() - } -} - -func TestValidBlockBadTarget(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - b.Target = BigIntToHash(util.BigAdd(MaxTarget, c.Big1)) - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadTarget { - t.Fail() - } - - b.Target = BigIntToHash(util.BigSub(MinTarget, c.Big1)) - valid, code = bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadTarget { - t.Fail() - } -} - -func TestValidBlock(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - - valid, code := bc.ValidBlock(b) - - if !valid { - t.Fail() - } - if code != ValidBlock { - t.Fail() - } -} - -func TestBlockDoubleSpend(t *testing.T) { - // block should fail to be valid if there exists two transactions - // referencing the same input, but output > input (double spend attack) - bc, b := NewValidTestChainAndBlock() - b.Transactions = append(b.Transactions, b.Transactions[1]) - - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != DoubleSpend { - t.Fail() - } -} - -func TestValidBlockBigNumber(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - b.BlockNumber = uint32(len(bc.Blocks)) + 1 - - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadBlockNumber { - t.Fail() - } -} - -func TestValidBlockBadCloudBaseTransaction(t *testing.T) { - bc, b := NewValidTestChainAndBlock() - b.Transactions[0] = NewTestTransaction() - - valid, code := bc.ValidBlock(b) - - if valid { - t.Fail() - } - if code != BadCloudBaseTransaction { - t.Fail() - } -} - -func TestValidCloudBaseNilCloudBase(t *testing.T) { - valid, code := ValidCloudBase(nil) - - if valid { - t.Fail() - } - if code != NilCloudBaseTransaction { - t.Fail() - } -} - -func TestValidCloudBaseTransaction(t *testing.T) { - cbTx, _ := NewValidCloudBaseTestTransaction() - valid, code := ValidCloudBase(cbTx) - - if !valid { - t.Fail() - } - if code != ValidCloudBaseTransaction { - t.Fail() - } -} - -func TestValidCloudBaseBadSender(t *testing.T) { - w := NewWallet() - cbTx, _ := NewValidCloudBaseTestTransaction() - cbTx.Sender = w.Public() - valid, code := ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseSender { - t.Fail() - } -} - -func TestValidCloudBaseBadBadInput(t *testing.T) { - cbTx, _ := NewValidCloudBaseTestTransaction() - cbTx.TxBody.Input.BlockNumber = 1 - valid, code := ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseInput { - t.Fail() - } - - cbTx, _ = NewValidCloudBaseTestTransaction() - cbTx.TxBody.Input.Hash = NewTestHash() - valid, code = ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseInput { - t.Fail() - } - - cbTx, _ = NewValidCloudBaseTestTransaction() - cbTx.TxBody.Input.Index = 1 - valid, code = ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseInput { - t.Fail() - } -} - -func TestValidCloudBaseBadOutput(t *testing.T) { - cbTx, _ := NewValidCloudBaseTestTransaction() - w := NewWallet() - cbTx.Outputs = append(cbTx.Outputs, TxOutput{25, w.Public()}) - valid, code := ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseOutput { - t.Fail() - } - - cbTx, _ = NewValidCloudBaseTestTransaction() - var emptyOutputs []TxOutput - cbTx.Outputs = emptyOutputs - valid, code = ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseOutput { - t.Fail() - } - - cbTx, _ = NewValidCloudBaseTestTransaction() - cbTx.Outputs[0].Amount = 0 - valid, code = ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseOutput { - t.Fail() - } - - cbTx, _ = NewValidCloudBaseTestTransaction() - cbTx.Outputs[0].Recipient = NilAddr - valid, code = ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseOutput { - t.Fail() - } -} - -func TestValidCloudBaseBadSig(t *testing.T) { - cbTx, _ := NewValidCloudBaseTestTransaction() - w := NewWallet() - cbTx.Sig, _ = w.Sign(NewTestHash(), rand.Reader) - valid, code := ValidCloudBase(cbTx) - - if valid { - t.Fail() - } - if code != BadCloudBaseSig { - t.Fail() - } -} - -func TestValidGenesisBlock(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if !valid { - t.Fail() - } - - if code != ValidGenesisBlock { - t.Fail() - } -} - -func TestValidGenesisBlockNilGenesisBlock(t *testing.T) { - - bc := &BlockChain{ - Blocks: []*Block{nil}, - Head: NewTestHash(), - } - - valid, code := bc.ValidGenesisBlock(nil) - - if valid { - t.Fail() - } - - if code != NilGenesisBlock { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisBlockNumber(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.BlockHeader.BlockNumber = 1 - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisBlockNumber { - t.Fail() - } - - gb.BlockHeader.BlockNumber = 0 - bc = &BlockChain{ - Blocks: []*Block{NewTestBlock(), gb}, - Head: HashSum(gb), - } - - valid, code = bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisBlockNumber { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisLastBlock(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.LastBlock = NewTestHash() - - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisLastBlock { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisTransactions(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.Transactions = append(gb.Transactions, NewTestTransaction()) - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisTransactions { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisCloudBaseTransaction(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.Transactions[0] = NewTestTransaction() - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisCloudBaseTransaction { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisTarget(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.Target = BigIntToHash(util.BigExp(2, 255)) - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisTarget { - t.Fail() - } -} - -func TestValidGenesisBlockBadGenesisTime(t *testing.T) { - miner := NewWallet() - currentTarget := BigIntToHash(MaxTarget) - currentBlockReward := uint64(25) - gb := Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) - gb.Time = 0 - bc := &BlockChain{ - Blocks: []*Block{gb}, - Head: HashSum(gb), - } - - valid, code := bc.ValidGenesisBlock(gb) - - if valid { - t.Fail() - } - - if code != BadGenesisTime { - t.Fail() - } -} diff --git a/common/constants/big.go b/common/constants/big.go index 56ad52e..9b1f50c 100644 --- a/common/constants/big.go +++ b/common/constants/big.go @@ -12,12 +12,25 @@ var ( Big0 = big.NewInt(0) // Big1 is 1 represented as type "math/big" Big1 = big.NewInt(1) + // Big2Exp232 is 2^232 respresented as type "math/big" + Big2Exp232 = util.BigExp(2, 232) // Big2Exp256 is 2^256 respresented as type "math/big" Big2Exp256 = util.BigExp(2, 256) ) // Commonly used max values represented as type "math/big" var ( + // MaxUint256 is the maximum uint232 number represented as type "math/big" + MaxUint232 = util.BigSub(Big2Exp232, Big1) // MaxUint256 is the maximum uint256 number represented as type "math/big" MaxUint256 = util.BigSub(Big2Exp256, Big1) ) + +var ( + // MaxTarget is the maximum target, which also represents the minimum + // difficulty + MaxTarget = MaxUint256 + // MinTarget is the minimum target, which also represents the maximum + // difficulty + MinTarget = Big1 +) diff --git a/consensus/consensus.go b/consensus/consensus.go index d98d90a..c47faa1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -1,103 +1,237 @@ package consensus import ( - "math/big" + "crypto/ecdsa" + "math" log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/blockchain" + c "github.com/ubclaunchpad/cumulus/common/constants" ) -// MinedBlockCode is returned from ValidateMinedBlock -type MinedBlockCode uint32 - -const ( - // ValidNewBlock is returned when a mined block is valid - ValidNewBlock MinedBlockCode = iota - // BadBlock is returned when an invalid block was mined - BadBlock MinedBlockCode = iota - // BadNonce is returned when the hash of the block is not less than the - // target - BadNonce MinedBlockCode = iota - // BadTarget is returned when the target that was used while mining is - // not equal to the current network target - BadTarget MinedBlockCode = iota - // BadBlockReward is returned when the block's rewards is not equal to the - // current reward of the network - BadBlockReward MinedBlockCode = iota - // BadCloudBase is returned when the address of the recipient of the - // CloudBase transaction is not valid - BadCloudBase MinedBlockCode = iota -) +// VerifyTransaction tests whether a transaction valid. +func VerifyTransaction(bc *blockchain.BlockChain, + t *blockchain.Transaction) (bool, TransactionCode) { -const ( - // blockRewardHalvingRate is the number of blocks that need to be mined - // before the blockReward is halved - blockRewardHalvingRate int = 210000 -) + // Check if the transaction is equal to nil + if t == nil { + return false, NilTransaction + } -var ( - // BlockReward is the current reward for mining a block - BlockReward uint64 = 25 - // CurrentDifficulty is the current hashing difficulty of the network - CurrentDifficulty = blockchain.MinTarget -) + // Find the transaction input in the chain (by hash) + var input *blockchain.Transaction + input = bc.GetInputTransaction(t) + if input == nil || blockchain.HashSum(input) != t.Input.Hash { + return false, NoInputTransaction + } + + // Check that output to sender in input is equal to outputs in t + if !input.InputsEqualOutputs(t) { + return false, Overspend + } -// HalveReward halves the current blockReward if the size of the BlockChain is a -// multiple of the blockRewardHalvingRate -func HalveReward(bc *blockchain.BlockChain) { - if len(bc.Blocks)%blockRewardHalvingRate == 0 { - BlockReward /= 2 + // 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 { + return false, Respend + } + + return true, ValidTransaction } -// CurrentTarget returns the current target based on the CurrentDifficulty -func CurrentTarget() blockchain.Hash { - return blockchain.BigIntToHash( - new(big.Int).Div( - blockchain.MaxTarget, - CurrentDifficulty, - ), - ) +// VerifyCloudBase returns true if a transaction is a valid CloudBase transaction +// and false otherwise +func VerifyCloudBase(bc *blockchain.BlockChain, + t *blockchain.Transaction) (bool, CloudBaseTransactionCode) { + + // Check if the CloudBase transaction is equal to nil. + if t == nil { + return false, NilCloudBaseTransaction + } + + // Check that the sender address is nil. + if t.Sender != blockchain.NilAddr { + 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 { + return false, BadCloudBaseInput + } + + // Search for the block associated with the CloudBase transaction. If the + // transaction is not found, then it is to be added to the next block in the + // blockchain. + var i int + for i = 0; i < len(bc.Blocks); i++ { + if b, _ := bc.Blocks[i].ContainsTransaction(t); b { + break + } + } + + // Determine the reward associated with that specific block. + timesHalved := float64(((i + 1) / blockRewardHalvingRate)) + reward := StartingBlockReward / uint64(math.Pow(float64(2), timesHalved)) + + // Check that the output is properly set. + if len(t.Outputs) != 1 || t.Outputs[0].Recipient == blockchain.NilAddr { + return false, BadCloudBaseOutput + } + + // Check that the reward is properly set. + if t.Outputs[0].Amount != reward { + return false, BadCloudBaseReward + } + + // Assert that the signature is equal to nil. + if t.Sig != blockchain.NilSig { + return false, BadCloudBaseSig + } + + return true, ValidCloudBaseTransaction } -// ValidMinedBlock validates that the mined block conforms to the -// consensus rules of Cumulus -func ValidMinedBlock( - cb blockchain.Address, - bc *blockchain.BlockChain, - b *blockchain.Block) (bool, MinedBlockCode) { +// VerifyGenesisBlock checks whether a block is a valid genesis block. +func VerifyGenesisBlock(bc *blockchain.BlockChain, + gb *blockchain.Block) (bool, GenesisBlockCode) { + + // Check if the genesis block is equal to nil. + if gb == nil { + return false, NilGenesisBlock + } - // Check if the block is valid - if valid, code := bc.ValidBlock(b); !valid { - log.Errorf("Invalid block, BlockCode: %d", code) - return false, BadBlock + // Check if the genesis block's block number is equal to 0. + if gb.BlockHeader.BlockNumber != 0 || + bc.Blocks[0] != gb { + return false, BadGenesisBlockNumber } - // Check if the CloudBase transaction reward is equal to the network's - // current block reward - if b.GetCloudBaseTransaction().Outputs[0].Amount != BlockReward { - log.Error("Invalid Block Reward") - return false, BadBlockReward + // Check if the genesis block's last block hash is equal to 0. + if blockchain.HashToBigInt(gb.BlockHeader.LastBlock).Cmp(c.Big0) != 0 { + return false, BadGenesisLastBlock } - // Check if the CloudBase transaction recipient is equal to the address - // of the miner that mined the block - if b.GetCloudBaseTransaction().Outputs[0].Recipient != cb { - log.Error("Invalid CloudBase address") - return false, BadCloudBase + // Check if the size of the transaction list is equal to 1. + if len(gb.Transactions) != 1 { + return false, BadGenesisTransactions } - // Check if the block's target is equal to the network's current target - if b.Target != CurrentTarget() { - log.Error("Invalid Target") + // Check if the transaction is a valid cloud base transaction. + if valid, code := VerifyCloudBase(bc, gb.Transactions[0]); !valid { + log.Errorf("Invalid CloudBase, CloudBaseTransactionCode: %d", code) + return false, BadGenesisCloudBaseTransaction + } + + // Check that the target is within the min and max difficulty levels. + // TODO: CurrentTarget() is used because the difficulty is static for v1 of + // cumulus. Once the difficulty is dynamic, the target would need to be + // compared to the target calculated for that specific block. + target := blockchain.HashToBigInt(gb.Target) + if target.Cmp(c.MaxTarget) == 1 || + target.Cmp(c.MinTarget) == -1 || + target.Cmp(blockchain.HashToBigInt(CurrentTarget())) != 0 { + return false, BadGenesisTarget + } + + // Check that time is not greater than current time or equal to 0. + if uint32(gb.Time) == 0 { + return false, BadGenesisTime + } + + return true, ValidGenesisBlock +} + +// VerifyBlock checks whether a block is valid. +func VerifyBlock(bc *blockchain.BlockChain, + b *blockchain.Block) (bool, BlockCode) { + + // Check if the block is equal to nil. + if b == nil { + return false, NilBlock + } + + // Check if the block is the genesis block. + if b.BlockHeader.BlockNumber == 0 || bc.Blocks[0] == b { + if valid, code := VerifyGenesisBlock(bc, b); !valid { + log.Errorf("Invalid GenesisBlock, GenesisBlockCode: %d", code) + return false, BadGenesisBlock + } + return true, ValidBlock + } + + // Check that block number is between 0 and max blocks. + ix := b.BlockNumber - 1 + if int(ix) > len(bc.Blocks)-1 || ix < 0 { + return false, BadBlockNumber + } + + // Check that block number is one greater than last block + lastBlock := bc.Blocks[ix] + if lastBlock.BlockNumber != ix { + return false, BadBlockNumber + } + + // Check that the first transaction is a CloudBase transaction + if valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()); !valid { + log.Errorf("Invalid CloudBase, CloudBaseTransactionCode: %d", code) + return false, BadCloudBaseTransaction + } + + // Verify every Transaction in the block. + for _, t := range b.Transactions[1:] { + if valid, code := VerifyTransaction(bc, t); !valid { + log.Errorf("Invalid Transaction, TransactionCode: %d", code) + return false, BadTransaction + } + } + + // Check that the target is within the min and max difficulty levels and + // that the target is correct. + // TODO: CurrentTarget() is used because the difficulty is static for v1 of + // cumulus. Once the difficulty is dynamic, the target would need to be + // compared to the target calculated for that specific block. + target := blockchain.HashToBigInt(b.Target) + if target.Cmp(c.MaxTarget) == 1 || + target.Cmp(c.MinTarget) == -1 || + target.Cmp(blockchain.HashToBigInt(CurrentTarget())) != 0 { return false, BadTarget } + // Check that time is not greater than current time or equal to 0 + if uint32(b.Time) == 0 { + return false, BadTime + } + + // Check that hash of last block is correct + if blockchain.HashSum(lastBlock) != b.LastBlock { + return false, BadHash + } + // Verify proof of work if !blockchain.HashSum(b).LessThan(b.Target) { - log.Error("Invalid Nonce, no proof of work") return false, BadNonce } - return true, ValidNewBlock + // 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 + } + } + } + } + + return true, ValidBlock } diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go index 07e3419..3fef4fa 100644 --- a/consensus/consensus_test.go +++ b/consensus/consensus_test.go @@ -1,166 +1,652 @@ package consensus import ( + "fmt" + "math/rand" "testing" - "math/rand" + crand "crypto/rand" "github.com/ubclaunchpad/cumulus/blockchain" c "github.com/ubclaunchpad/cumulus/common/constants" "github.com/ubclaunchpad/cumulus/common/util" ) -func TestHalveReward(t *testing.T) { - bc, _ := blockchain.NewValidTestChainAndBlock() - tempBlockReward := BlockReward +// VerifyTransaction Tests + +func TestVerifyTransactionNilTransaction(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() - for i := 0; i < blockRewardHalvingRate-2; i++ { - HalveReward(bc) - if BlockReward != tempBlockReward { - BlockReward = tempBlockReward - t.Fail() - } - bc.AppendBlock(new(blockchain.Block)) + valid, code := VerifyTransaction(bc, nil) + + if valid { + t.Fail() + } + if code != NilTransaction { + t.Fail() } +} + +func TestVerifyTransactionNoInputTransaction(t *testing.T) { + tr, _ := blockchain.NewTestTransactionValue( + blockchain.NewWallet(), + blockchain.NewWallet(), + 1, + 0, + ) + bc, _ := blockchain.NewValidBlockChainFixture() - HalveReward(bc) - if BlockReward != tempBlockReward/2 { - BlockReward = tempBlockReward + valid, code := VerifyTransaction(bc, tr) + + if valid { + t.Fail() + } + if code != NoInputTransaction { t.Fail() } - BlockReward = tempBlockReward } -func TestCurrentTarget(t *testing.T) { - if blockchain.HashToBigInt(CurrentTarget()).Cmp(blockchain.MaxTarget) != 0 { +func TestVerifyTransactionOverspend(t *testing.T) { + // 2 + 2 = 5 ? + bc, _ := blockchain.NewValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[1] + tr.Outputs[0].Amount = 5 + + valid, code := VerifyTransaction(bc, tr) + + if valid { + t.Fail() + } + if code != Overspend { + fmt.Println(code) t.Fail() } } -func TestValidMinedBlockBadBlock(t *testing.T) { - w := blockchain.NewWallet() - valid, code := ValidMinedBlock(w.Public(), nil, nil) +func TestVerifyTransactionSignatureFail(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[1] + fakeSender := blockchain.NewWallet() + tr, _ = tr.TxBody.Sign(fakeSender, crand.Reader) + bc.Blocks[1].Transactions[1] = tr + + valid, code := VerifyTransaction(bc, tr) if valid { t.Fail() } + if code != BadSig { + t.Fail() + } +} + +func TestVerifyTransactionPass(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + tr := b.Transactions[1] - if code != BadBlock { + valid, code := VerifyTransaction(bc, tr) + + if !valid { + t.Fail() + } + if code != ValidTransaction { t.Fail() } } -func TestValidMinedBlockBadBlockReward(t *testing.T) { - bc, b, a := newValidBlockChainAndCloudBaseBlock() +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) - var r uint64 - for r = RandomUint64(); r == BlockReward; r = RandomUint64() { + valid, code := VerifyTransaction(bc, trC) + + if valid { + t.Fail() + } + if code != Respend { + t.Fail() + } +} + +// VerifyBlock Tests + +func TestVerifyBlockNilBlock(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + + valid, code := VerifyBlock(bc, nil) + + if valid { + t.Fail() + } + if code != NilBlock { + t.Fail() + } +} + +func TestVerifyBlockBadNonce(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.Target = blockchain.BigIntToHash(c.Big1) + CurrentDifficulty = c.MaxTarget + valid, code := VerifyBlock(bc, b) + CurrentDifficulty = c.MinTarget + + if valid { + t.Fail() } - b.Transactions[0].Outputs[0].Amount = r + if code != BadNonce { + t.Fail() + } +} + +func TestVerifyBlockBadGenesisBlock(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.Target = blockchain.BigIntToHash(util.BigExp(2, 255)) + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } - valid, code := ValidMinedBlock(a, bc, b) + valid, code := VerifyBlock(bc, gb) if valid { t.Fail() } - if code != BadBlockReward { + if code != BadGenesisBlock { + t.Fail() + } +} + +func TestVerifyBlockBadTransaction(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + tr := bc.Blocks[1].Transactions[1] + tr.Outputs[0].Amount = 5 + + valid, code := VerifyBlock(bc, bc.Blocks[1]) + + if valid { + t.Fail() + } + if code != BadTransaction { + t.Fail() + } +} + +func TestVerifyBlockBadBlockNumber(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + bc.Blocks[1].BlockNumber = 2 + + valid, code := VerifyBlock(bc, bc.Blocks[1]) + + if valid { t.Fail() } + if code != BadBlockNumber { + t.Fail() + } +} + +func TestVerifyBlockBadHash(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.BlockHeader.LastBlock = blockchain.NewTestHash() + + valid, code := VerifyBlock(bc, b) + if valid { + t.Fail() + } + if code != BadHash { + t.Fail() + } } -func TestValidMinedBlockBadTarget(t *testing.T) { - bc, b, a := newValidBlockChainAndCloudBaseBlock() - b.Target = blockchain.NewValidTestTarget() +func TestVerifyBlockBadTime(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.Time = 0 + valid, code := VerifyBlock(bc, b) - valid, code := ValidMinedBlock(a, bc, b) + if valid { + t.Fail() + } + if code != BadTime { + t.Fail() + } +} + +func TestVerifyBlockBadTarget(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.Target = blockchain.BigIntToHash(util.BigAdd(c.MaxTarget, c.Big1)) + valid, code := VerifyBlock(bc, b) if valid { t.Fail() } + if code != BadTarget { + t.Fail() + } + b.Target = blockchain.BigIntToHash(util.BigSub(c.MinTarget, c.Big1)) + valid, code = VerifyBlock(bc, b) + + if valid { + t.Fail() + } if code != BadTarget { t.Fail() } } -func TestValidMinedBlockBadNonce(t *testing.T) { - bc, b, a := newValidBlockChainAndCloudBaseBlock() - b.Target = blockchain.BigIntToHash(c.Big1) - tempCurrentDifficulty := CurrentDifficulty - CurrentDifficulty = blockchain.MaxTarget +func TestVerifyBlock(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() - valid, code := ValidMinedBlock(a, bc, b) + valid, code := VerifyBlock(bc, b) - CurrentDifficulty = tempCurrentDifficulty + if !valid { + t.Fail() + } + if code != ValidBlock { + t.Fail() + } +} + +func TestBlockDoubleSpend(t *testing.T) { + // block should fail to be valid if there exists two transactions + // referencing the same input, but output > input (double spend attack) + bc, b := blockchain.NewValidTestChainAndBlock() + b.Transactions = append(b.Transactions, b.Transactions[1]) + + valid, code := VerifyBlock(bc, b) if valid { t.Fail() } + if code != DoubleSpend { + t.Fail() + } +} - if code != BadNonce { +func TestVerifyBlockBigNumber(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.BlockNumber = uint32(len(bc.Blocks)) + 1 + + valid, code := VerifyBlock(bc, b) + + if valid { + t.Fail() + } + if code != BadBlockNumber { t.Fail() } } -func TestValidMinedBlockBadCloudBase(t *testing.T) { - bc, b, a := newValidBlockChainAndCloudBaseBlock() - b.Transactions[0].Outputs[0].Recipient = blockchain.NewWallet().Public() - valid, code := ValidMinedBlock(a, bc, b) +func TestVerifyBlockBadCloudBaseTransaction(t *testing.T) { + bc, b := blockchain.NewValidTestChainAndBlock() + b.Transactions[0] = blockchain.NewTestTransaction() + + valid, code := VerifyBlock(bc, b) if valid { t.Fail() } + if code != BadCloudBaseTransaction { + t.Fail() + } +} + +// VerifyCloudBase Tests + +func TestVerifyCloudBaseNilCloudBase(t *testing.T) { + bc, _ := blockchain.NewValidTestChainAndBlock() + valid, code := VerifyCloudBase(bc, nil) - if code != BadCloudBase { + if valid { + t.Fail() + } + if code != NilCloudBaseTransaction { t.Fail() } } -func TestValidMinedBlock(t *testing.T) { - tempMaxTarget := blockchain.MaxTarget - tempCurrentDifficulty := CurrentDifficulty +func TestVerifyCloudBaseBadCloudBaseBlockReward(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + b := bc.Blocks[0] + b.Transactions[0].Outputs[0].Amount = CurrentBlockReward(bc) + 1 + + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) - blockchain.MaxTarget = c.MaxUint256 - CurrentDifficulty = blockchain.MinTarget + if valid { + t.Fail() + } - bc, b, a := newValidBlockChainAndCloudBaseBlock() + if code != BadCloudBaseReward { + t.Fail() + } - valid, code := ValidMinedBlock(a, bc, b) +} - blockchain.MaxTarget = tempMaxTarget - CurrentDifficulty = tempCurrentDifficulty +func TestVerifyCloudBaseTransaction(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + b := bc.Blocks[0] + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) if !valid { t.Fail() } + if code != ValidCloudBaseTransaction { + t.Fail() + } +} - if code != ValidNewBlock { +func TestVerifyCloudBaseBadSender(t *testing.T) { + w := blockchain.NewWallet() + bc, _ := blockchain.NewValidBlockChainFixture() + b := bc.Blocks[0] + b.GetCloudBaseTransaction().Sender = w.Public() + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseSender { t.Fail() } } -func newValidBlockChainAndCloudBaseBlock() ( - *blockchain.BlockChain, - *blockchain.Block, - blockchain.Address) { +func TestVerifyCloudBaseBadBadInput(t *testing.T) { bc, _ := blockchain.NewValidBlockChainFixture() - cbTx, a := blockchain.NewValidCloudBaseTestTransaction() - bcSize := uint32(len(bc.Blocks)) - b := &blockchain.Block{ - BlockHeader: blockchain.BlockHeader{ - BlockNumber: bcSize, - LastBlock: blockchain.HashSum(bc.Blocks[bcSize-1]), - Target: CurrentTarget(), - Time: util.UnixNow(), - Nonce: 0, - }, - Transactions: make([]*blockchain.Transaction, 1), - } - b.Transactions[0] = cbTx - return bc, b, a + b := bc.Blocks[0] + b.GetCloudBaseTransaction().TxBody.Input.BlockNumber = 1 + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseInput { + t.Fail() + } + + bc, _ = blockchain.NewValidBlockChainFixture() + b = bc.Blocks[0] + b.GetCloudBaseTransaction().TxBody.Input.Hash = blockchain.NewTestHash() + valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseInput { + t.Fail() + } + + bc, _ = blockchain.NewValidBlockChainFixture() + b = bc.Blocks[0] + b.GetCloudBaseTransaction().TxBody.Input.Index = 1 + valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseInput { + t.Fail() + } +} + +func TestVerifyCloudBaseBadOutput(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + b := bc.Blocks[0] + w := blockchain.NewWallet() + b.GetCloudBaseTransaction().Outputs = + append( + b.GetCloudBaseTransaction().Outputs, + blockchain.TxOutput{ + Amount: 25, + Recipient: w.Public(), + }, + ) + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseOutput { + t.Fail() + } + + bc, _ = blockchain.NewValidBlockChainFixture() + b = bc.Blocks[0] + var emptyOutputs []blockchain.TxOutput + b.GetCloudBaseTransaction().Outputs = emptyOutputs + valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseOutput { + t.Fail() + } + + bc, _ = blockchain.NewValidBlockChainFixture() + b = bc.Blocks[0] + b.GetCloudBaseTransaction().Outputs[0].Recipient = blockchain.NilAddr + valid, code = VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseOutput { + t.Fail() + } +} + +func TestVerifyCloudBaseBadSig(t *testing.T) { + bc, _ := blockchain.NewValidBlockChainFixture() + b := bc.Blocks[0] + w := blockchain.NewWallet() + b.GetCloudBaseTransaction().Sig, _ = + w.Sign(blockchain.NewTestHash(), crand.Reader) + valid, code := VerifyCloudBase(bc, b.GetCloudBaseTransaction()) + + if valid { + t.Fail() + } + if code != BadCloudBaseSig { + t.Fail() + } +} + +// VerifyGenesisBlock Tests + +func TestVerifyGenesisBlock(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if !valid { + t.Fail() + } + + if code != ValidGenesisBlock { + t.Fail() + } +} + +func TestVerifyGenesisBlockNilGenesisBlock(t *testing.T) { + + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{nil}, + Head: blockchain.NewTestHash(), + } + + valid, code := VerifyGenesisBlock(bc, nil) + + if valid { + t.Fail() + } + + if code != NilGenesisBlock { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisBlockNumber(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.BlockHeader.BlockNumber = 1 + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisBlockNumber { + t.Fail() + } + + gb.BlockHeader.BlockNumber = 0 + bc = &blockchain.BlockChain{ + Blocks: []*blockchain.Block{blockchain.NewTestBlock(), gb}, + Head: blockchain.HashSum(gb), + } + + valid, code = VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisBlockNumber { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisLastBlock(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.LastBlock = blockchain.NewTestHash() + + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisLastBlock { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisTransactions(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.Transactions = append(gb.Transactions, blockchain.NewTestTransaction()) + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisTransactions { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisCloudBaseTransaction(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.Transactions[0] = blockchain.NewTestTransaction() + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisCloudBaseTransaction { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisTarget(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.Target = blockchain.BigIntToHash(util.BigExp(2, 255)) + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisTarget { + t.Fail() + } +} + +func TestVerifyGenesisBlockBadGenesisTime(t *testing.T) { + miner := blockchain.NewWallet() + currentTarget := blockchain.BigIntToHash(c.MaxTarget) + currentBlockReward := uint64(25) + gb := blockchain.Genesis(miner.Public(), currentTarget, currentBlockReward, []byte{}) + gb.Time = 0 + bc := &blockchain.BlockChain{ + Blocks: []*blockchain.Block{gb}, + Head: blockchain.HashSum(gb), + } + + valid, code := VerifyGenesisBlock(bc, gb) + + if valid { + t.Fail() + } + + if code != BadGenesisTime { + t.Fail() + } } // Creates a random uint64 value diff --git a/consensus/current.go b/consensus/current.go new file mode 100644 index 0000000..76a788f --- /dev/null +++ b/consensus/current.go @@ -0,0 +1,39 @@ +package consensus + +import ( + "math/big" + + "github.com/ubclaunchpad/cumulus/blockchain" + c "github.com/ubclaunchpad/cumulus/common/constants" +) + +const ( + // StartingBlockReward is the mining reward that the blockchain will begin + // with. + StartingBlockReward uint64 = 25 + // blockRewardHalvingRate is the number of blocks that need to be mined + // before the blockReward is halved + blockRewardHalvingRate int = 210000 +) + +var ( + // CurrentDifficulty is the current hashing difficulty of the network + CurrentDifficulty = c.MinTarget +) + +// CurrentBlockReward determines the current block reward using the +// the length of the blockchain +func CurrentBlockReward(bc *blockchain.BlockChain) uint64 { + return StartingBlockReward / + uint64(((len(bc.Blocks) / blockRewardHalvingRate) + 1)) +} + +// CurrentTarget returns the current target based on the CurrentDifficulty +func CurrentTarget() blockchain.Hash { + return blockchain.BigIntToHash( + new(big.Int).Div( + c.MaxTarget, + CurrentDifficulty, + ), + ) +} diff --git a/consensus/errors.go b/consensus/errors.go new file mode 100644 index 0000000..c6f81f6 --- /dev/null +++ b/consensus/errors.go @@ -0,0 +1,106 @@ +package consensus + +// TransactionCode is returned from ValidTransaction. +type TransactionCode uint32 + +// CloudBaseTransactionCode is returned from ValidCloudBaseTransaction. +type CloudBaseTransactionCode uint32 + +// GenesisBlockCode is returned from ValidGenesisBlock. +type GenesisBlockCode uint32 + +// BlockCode is returned from ValidBlock. +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 + // Overspend is returned when transaction outputs exceed transaction inputs. + Overspend + // BadSig is returned when the signature verification fails. + BadSig + // Respend is returned when inputs have been spent elsewhere in the chain. + Respend + // NilTransaction is returned when the transaction pointer is nil. + NilTransaction +) + +const ( + // ValidCloudBaseTransaction is returned when a transaction is a valid + // CloudBase transaction. + ValidCloudBaseTransaction CloudBaseTransactionCode = iota + // BadCloudBaseSender is returned when the sender address in the CloudBase + // transaction is not a NilAddr. + BadCloudBaseSender + // BadCloudBaseInput is returned when all the fields inf the CloudBase + // transaction input are not equal to 0. + BadCloudBaseInput + // BadCloudBaseOutput is returned when the CloudBase transaction output is + // invalid. + BadCloudBaseOutput + // BadCloudBaseReward is returned when the CloudBase transaction reward is + // invalid. + BadCloudBaseReward + // BadCloudBaseSig is returned when the CloudBase transaction signature is + // not equal to NilSig. + BadCloudBaseSig + // NilCloudBaseTransaction is returned when the CloudBase transaction + // pointer is nil + NilCloudBaseTransaction +) + +const ( + // ValidGenesisBlock is returned when a block is a valid genesis block. + ValidGenesisBlock GenesisBlockCode = iota + // BadGenesisLastBlock is returned when the LastBlock of the genesis block + // is not equal to 0. + BadGenesisLastBlock + // BadGenesisTransactions is returned when the genesis block does not contain + // exactly 1 transaction, the first CloudBase transaction. + BadGenesisTransactions + // BadGenesisCloudBaseTransaction is returned when the transaction in the + // genesis block is not a valid CloudBase transaction. + BadGenesisCloudBaseTransaction + // BadGenesisBlockNumber is returned when the block number in the genesis + // block is not equal to 0. + BadGenesisBlockNumber + // BadGenesisTarget is returned when the genesis block's target is invalid. + BadGenesisTarget + // BadGenesisTime is returned when the genesis block's time is invalid. + BadGenesisTime + // NilGenesisBlock is returned when the genesis block is equal to nil. + NilGenesisBlock +) + +const ( + // ValidBlock is returned when the block is valid. + ValidBlock BlockCode = iota + // BadTransaction is returned when the block contains an invalid + // transaction. + BadTransaction + // BadTime is returned when the block contains an invalid time. + BadTime + // BadTarget is returned when the block contains an invalid target. + BadTarget + // BadBlockNumber is returned when block number is not one greater than + // previous block. + BadBlockNumber + // BadHash is returned when the block contains incorrect hash. + BadHash + // DoubleSpend is returned when two transactions in the block share inputs, + // but outputs > inputs. + DoubleSpend + // BadCloudBaseTransaction is returned when a block does not have a + // CloudBase transaction as the first transaction in its list of + // transactions. + BadCloudBaseTransaction + // BadGenesisBlock is returned if the block is a genesis block and is + // invalid. + BadGenesisBlock + // BadNonce is returned if the nonce is invalid. + BadNonce + // NilBlock is returned when the block pointer is nil. + NilBlock +) diff --git a/miner/miner.go b/miner/miner.go index e5a777a..a32cbc8 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -4,7 +4,6 @@ import ( "math" "sync" - log "github.com/Sirupsen/logrus" "github.com/ubclaunchpad/cumulus/blockchain" "github.com/ubclaunchpad/cumulus/common/util" "github.com/ubclaunchpad/cumulus/consensus" @@ -42,13 +41,6 @@ func RestartMiner(bc *blockchain.BlockChain, b *blockchain.Block) { // TODO: Make Mine take an interface with a callback as an arguement. func Mine(bc *blockchain.BlockChain, b *blockchain.Block) *MiningResult { setStart() - if valid, _ := bc.ValidBlock(b); !valid { - log.Error("miner given invalid block") - return &MiningResult{ - Complete: false, - Info: MiningNeverStarted, - } - } for !VerifyProofOfWork(b) { // Check if we should keep mining. @@ -111,7 +103,7 @@ func CloudBase( // Set the transaction amount to the BlockReward // TODO: Add transaction fees cbReward := blockchain.TxOutput{ - Amount: consensus.BlockReward, + Amount: consensus.CurrentBlockReward(bc), Recipient: cb, } cbTxBody := blockchain.TxBody{ diff --git a/miner/miner_test.go b/miner/miner_test.go index 3d752fe..2ec6a30 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -13,36 +13,28 @@ import ( func TestMine(t *testing.T) { bc, b := blockchain.NewValidTestChainAndBlock() - tempMaxTarget := blockchain.MaxTarget + tempMaxTarget := c.MaxTarget // Set min difficulty to be equal to the target so that the block validation // passes - blockchain.MaxTarget = c.MaxUint256 + c.MaxTarget = c.MaxUint256 // Set target to be as easy as possible so that we find a hash // below the target straight away (2**256 - 1) - b.Target = blockchain.BigIntToHash(blockchain.MaxTarget) + b.Target = blockchain.BigIntToHash(c.MaxTarget) b.Time = util.UnixNow() mineResult := Mine(bc, b) - blockchain.MaxTarget = tempMaxTarget + c.MaxTarget = tempMaxTarget assert.True(t, mineResult.Complete) assert.Equal(t, mineResult.Info, MiningSuccessful) } -func TestMineBadBlock(t *testing.T) { - bc, _ := blockchain.NewValidTestChainAndBlock() - mineResult := Mine(bc, nil) - - assert.False(t, mineResult.Complete) - assert.Equal(t, mineResult.Info, MiningNeverStarted) -} - func TestMineHaltMiner(t *testing.T) { bc, b := blockchain.NewValidTestChainAndBlock() // Set target to be as hard as possible so that we stall. - b.Target = blockchain.BigIntToHash(blockchain.MinTarget) + b.Target = blockchain.BigIntToHash(c.MinTarget) b.Time = util.UnixNow() // Use a thread to stop the miner a few moments after starting. @@ -75,11 +67,11 @@ func TestCloudBase(t *testing.T) { CloudBase(b, bc, w.Public()) - if valid, _ := bc.ValidBlock(b); !valid { + if valid, _ := consensus.VerifyBlock(bc, b); !valid { t.Fail() } - if b.Transactions[0].Outputs[0].Amount != consensus.BlockReward { + if b.Transactions[0].Outputs[0].Amount != consensus.CurrentBlockReward(bc) { t.Fail() } diff --git a/pool/pool.go b/pool/pool.go index e90400a..e47bf54 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -5,6 +5,7 @@ import ( "github.com/ubclaunchpad/cumulus/blockchain" "github.com/ubclaunchpad/cumulus/common/util" + "github.com/ubclaunchpad/cumulus/consensus" "github.com/ubclaunchpad/cumulus/miner" ) @@ -64,7 +65,7 @@ func getIndex(a []*PooledTransaction, target time.Time, low, high int) int { // 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 { - if ok, _ := bc.ValidTransaction(t); ok { + if ok, _ := consensus.VerifyTransaction(bc, t); ok { p.set(t) return true } @@ -100,7 +101,7 @@ func (p *Pool) Delete(t *blockchain.Transaction) { // 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 { + if ok, _ := consensus.VerifyBlock(bc, b); !ok { return false } for _, t := range b.Transactions {