// VerifyTransaction checks that the inputs to the transaction exist, // that the transaction does not create or destroy coins and that the // signatures on the transaction are valid func (bc Blockchain) VerifyTransaction(tx coin.Transaction) error { //CHECKLIST: DONE: check for duplicate ux inputs/double spending //CHECKLIST: DONE: check that inputs of transaction have not been spent //CHECKLIST: DONE: check there are no duplicate outputs // Q: why are coin hours based on last block time and not // current time? // A: no two computers will agree on system time. Need system clock // indepedent timing that everyone agrees on. fee values would depend on // local clock // Check transaction type and length // Check for duplicate outputs // Check for duplicate inputs // Check for invalid hash // Check for no inputs // Check for no outputs // Check for non 1e6 multiple coin outputs // Check for zero coin outputs // Check valid looking signatures if err := tx.Verify(); err != nil { return err } uxIn, err := bc.unspent.GetMultiple(tx.In) if err != nil { return err } // Checks whether ux inputs exist, // Check that signatures are allowed to spend inputs if err := tx.VerifyInput(uxIn); err != nil { return err } // Get the UxOuts we expect to have when the block is created. uxOut := coin.CreateUnspents(bc.Head().Head, tx) // Check that there are any duplicates within this set if uxOut.HasDupes() { return errors.New("Duplicate unspent outputs in transaction") } if DebugLevel1 { // Check that new unspents don't collide with existing. This should // also be checked in verifyTransactions for i := range uxOut { if bc.unspent.Has(uxOut[i].Hash()) { return errors.New("New unspent collides with existing unspent") } } } // Check that no coins are lost, and sufficient coins and hours are spent err = coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut) if err != nil { return err } return nil }
func TestVerifyTransactionSpending(t *testing.T) { ft := FakeTree{} bc := NewBlockchain(&ft, nil) bc.CreateGenesisBlock(genAddress, _genCoins, _genTime) // Overspending hours tx := coin.Transaction{} uxs := bc.GetUnspent().Array() tx.PushInput(uxs[0].Hash()) tx.PushOutput(genAddress, 1e6, uxs[0].Body.Hours) tx.PushOutput(genAddress, uxs[0].Body.Coins-1e6, 1) uxIn, err := bc.GetUnspent().GetMultiple(tx.In) assert.Nil(t, err) uxOut := coin.CreateUnspents(bc.Head().Head, tx) assertError(t, coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut), "Insufficient coin hours") // add block to blockchain. _, ux := addBlockToBlockchain(t, bc) // addBlockToBlockchain(t, bc) // Valid tx, _ = makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, 50) uxIn, err = bc.GetUnspent().GetMultiple(tx.In) assert.Nil(t, err) uxOut = coin.CreateUnspents(bc.Head().Head, tx) assert.Nil(t, coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut)) // Destroying coins tx = coin.Transaction{} tx.PushInput(ux.Hash()) tx.PushOutput(genAddress, 1e6, 100) tx.PushOutput(genAddress, 10e6, 100) uxIn, err = bc.GetUnspent().GetMultiple(tx.In) assert.Nil(t, err) uxOut = coin.CreateUnspents(bc.Head().Head, tx) err = coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut) assert.NotNil(t, err) assert.Equal(t, err.Error(), "Transactions may not create or destroy coins") assertError(t, coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut), "Transactions may not create or destroy coins") // Insufficient coins tx = coin.Transaction{} tx.PushInput(ux.Hash()) p, s := cipher.GenerateKeyPair() a := cipher.AddressFromPubKey(p) coins := ux.Body.Coins assert.True(t, coins > 1e6) tx.PushOutput(a, 1e6, 100) tx.PushOutput(genAddress, coins-1e6, 100) tx.SignInputs([]cipher.SecKey{genSecret}) tx.UpdateHeader() b, err := bc.NewBlockFromTransactions(coin.Transactions{tx}, bc.Time()+_incTime) assert.Nil(t, err) uxs, err = bc.ExecuteBlock(&b) assert.Nil(t, err) tx = coin.Transaction{} tx.PushInput(uxs[0].Hash()) tx.PushOutput(a, 10e6, 1) tx.SignInputs([]cipher.SecKey{s}) tx.UpdateHeader() uxIn, err = bc.GetUnspent().GetMultiple(tx.In) assert.Nil(t, err) uxOut = coin.CreateUnspents(bc.Head().Head, tx) assertError(t, coin.VerifyTransactionSpending(bc.Time(), uxIn, uxOut), "Insufficient coins") }