func TestCreateAndExecuteBlock(t *testing.T) { defer cleanupVisor() // Test as not master, should fail vc := newGenesisConfig(t) v := NewVisor(vc) assert.Panics(t, func() { v.CreateAndExecuteBlock() }) // Test as master, no txns vc = newMasterVisorConfig(t) v = NewVisor(vc) _, err := v.CreateAndExecuteBlock() assert.NotNil(t, err) assert.Equal(t, err.Error(), "No transactions") // Test as master, more txns than allowed vc.BlockCreationInterval = uint64(101) v = NewVisor(vc) txns := addValidTxns(t, v, 3) txns = coin.SortTransactions(txns, getFee) v.Config.MaxBlockSize = txns[0].Size() assert.Equal(t, len(v.blockchain.Blocks), 1) assert.Equal(t, len(v.blockSigs.Sigs), 1) sb, err := v.CreateAndExecuteBlock() assert.Nil(t, err) assert.Equal(t, len(sb.Block.Body.Transactions), 1) assert.Equal(t, len(v.blockchain.Blocks), 2) assert.Equal(t, len(v.blockSigs.Sigs), 2) assert.Equal(t, v.blockchain.Blocks[1], sb.Block) assert.Equal(t, v.blockSigs.Sigs[1], sb.Sig) assert.Equal(t, len(v.Unconfirmed.Txns), 2) assert.Equal(t, sb.Block.Head.Time-v.blockchain.Blocks[0].Head.Time, vc.BlockCreationInterval) rawTxns := v.Unconfirmed.RawTxns() rawTxns = coin.SortTransactions(rawTxns, getFee) assert.Equal(t, len(rawTxns), 2) for _, tx := range sb.Block.Body.Transactions { assert.NotEqual(t, tx.Hash(), rawTxns[0].Hash()) assert.NotEqual(t, tx.Hash(), rawTxns[1].Hash()) } assert.Equal(t, txns[1].Hash(), rawTxns[0].Hash()) assert.Equal(t, txns[2].Hash(), rawTxns[1].Hash()) assert.Nil(t, v.blockSigs.Verify(v.Config.MasterKeys.Public, v.blockchain)) // No txns, forcing NewBlockFromTransactions to fail v = NewVisor(vc) assert.Equal(t, len(v.Unconfirmed.Txns), 0) txns = addValidTxns(t, v, 3) v.Config.MaxBlockSize = 0 sb, err = v.CreateAndExecuteBlock() assert.NotNil(t, err) assert.Equal(t, len(v.blockchain.Blocks), 1) assert.Equal(t, len(v.blockSigs.Sigs), 1) assert.Equal(t, len(v.Unconfirmed.Txns), 3) }
func TestRawTxns(t *testing.T) { ut := NewUnconfirmedTxnPool() utxs := make(coin.Transactions, 4) for i := 0; i < len(utxs); i++ { utx := addUnconfirmedTxnToPool(ut) utxs[i] = utx.Txn } utxs = coin.SortTransactions(utxs, getFee) txns := ut.RawTxns() txns = coin.SortTransactions(txns, getFee) for i, tx := range txns { assert.Equal(t, utxs[i], tx) } }
// Creates a SignedBlock from pending transactions // Applies transaction limit constraint // Applies max block size constraint // Should order transactions by priority func (self *Blockchain) CreateBlock() (coin.Block, error) { //var sb SignedBlock if self.SecKey == (cipher.SecKey{}) { log.Panic("Only master chain can create blocks") } txns := self.Unconfirmed.RawTxns() //sort //arbritrate //truncate //TODO: sort by arrival time/announce time //TODO: filter valid first txns = coin.SortTransactions(txns, self.Blockchain.TransactionFee) txns = self.blockchain.ArbitrateTransactions(txns) nTxns := len(txns) if nTxns > MaxTransactionsPerBlock { txns = txns[:MaxTransactionsPerBlock] } txns = txns.TruncateBytesTo(MaxBlockSize) //cap at 32 KB //TODO: ERROR< NewBlockFromTransactions arbritates! b, err := self.blockchain.NewBlockFromTransactions(txns, uint64(time.Now().Unix())) //remove creation interval, from new block if err != nil { return b, err } return b, err }
func addValidTxns(t *testing.T, v *Visor, n int) coin.Transactions { txns := make(coin.Transactions, n) for i := 0; i < len(txns); i++ { txn, err := makeValidTxn(v) assert.Nil(t, err) txns[i] = txn } for _, txn := range txns { err, known := v.RecordTxn(txn) assert.Nil(t, err) assert.False(t, known) } txns = coin.SortTransactions(txns, getFee) assert.Equal(t, len(v.Unconfirmed.Txns), n) return txns }
// Creates a SignedBlock from pending transactions func (self *Visor) CreateBlock(when uint64) (SignedBlock, error) { var sb SignedBlock if !self.Config.IsMaster { log.Panic("Only master chain can create blocks") } if len(self.Unconfirmed.Txns) == 0 { return sb, errors.New("No transactions") } txns := self.Unconfirmed.RawTxns() txns = coin.SortTransactions(txns, self.Blockchain.TransactionFee) txns = txns.TruncateBytesTo(self.Config.MaxBlockSize) b, err := self.Blockchain.NewBlockFromTransactions(txns, when) if err != nil { return sb, err } return self.SignBlock(b), nil }
// Validates a set of Transactions, individually, against each other and // against the Blockchain. If firstFail is true, it will return an error // as soon as it encounters one. Else, it will return an array of // Transactions that are valid as a whole. It may return an error if // firstFalse is false, if there is no way to filter the txns into a valid // array, i.e. processTransactions(processTransactions(txn, false), true) // should not result in an error, unless all txns are invalid. // TODO: // - move arbitration to visor // - blockchain should have strict checking func (bc Blockchain) processTransactions(txns coin.Transactions, arbitrating bool) (coin.Transactions, error) { // Transactions need to be sorted by fee and hash before arbitrating if arbitrating { txns = coin.SortTransactions(txns, bc.TransactionFee) } //TODO: audit if len(txns) == 0 { if arbitrating { return txns, nil } // If there are no transactions, a block should not be made return nil, errors.New("No transactions") } skip := make(map[int]byte) uxHashes := make(coin.UxHashSet, len(txns)) for i, tx := range txns { // Check the transaction against itself. This covers the hash, // signature indices and duplicate spends within itself err := bc.VerifyTransaction(tx) if err != nil { if arbitrating { skip[i] = byte(1) continue } else { return nil, err } } // Check that each pending unspent will be unique uxb := coin.UxBody{ SrcTransaction: tx.Hash(), } for _, to := range tx.Out { uxb.Coins = to.Coins uxb.Hours = to.Hours uxb.Address = to.Address h := uxb.Hash() _, exists := uxHashes[h] if exists { if arbitrating { skip[i] = byte(1) continue } else { m := "Duplicate unspent output across transactions" return nil, errors.New(m) } } if DebugLevel1 { // Check that the expected unspent is not already in the pool. // This should never happen because its a hash collision if bc.unspent.Has(h) { if arbitrating { skip[i] = byte(1) continue } else { m := "Output hash is in the UnspentPool" return nil, errors.New(m) } } } uxHashes[h] = byte(1) } } // Filter invalid transactions before arbitrating between colliding ones if len(skip) > 0 { newtxns := make(coin.Transactions, len(txns)-len(skip)) j := 0 for i := range txns { if _, shouldSkip := skip[i]; !shouldSkip { newtxns[j] = txns[i] j++ } } txns = newtxns skip = make(map[int]byte) } // Check to ensure that there are no duplicate spends in the entire block, // and that we aren't creating duplicate outputs. Duplicate outputs // within a single Transaction are already checked by VerifyTransaction hashes := txns.Hashes() for i := 0; i < len(txns)-1; i++ { s := txns[i] for j := i + 1; j < len(txns); j++ { t := txns[j] if DebugLevel1 { if hashes[i] == hashes[j] { // This is a non-recoverable error for filtering, and // should never occur. It indicates a hash collision // amongst different txns. Duplicate transactions are // caught earlier, when duplicate expected outputs are // checked for, and will not trigger this. return nil, errors.New("Duplicate transaction") } } for a := range s.In { for b := range t.In { if s.In[a] == t.In[b] { if arbitrating { // The txn with the highest fee and lowest hash // is chosen when attempting a double spend. // Since the txns are sorted, we skip the 2nd // iterable skip[j] = byte(1) } else { m := "Cannot spend output twice in the same block" return nil, errors.New(m) } } } } } } // Filter the final results, if necessary if len(skip) > 0 { newtxns := make(coin.Transactions, len(txns)-len(skip)) j := 0 for i := range txns { if _, shouldSkip := skip[i]; !shouldSkip { newtxns[j] = txns[i] j++ } } return newtxns, nil } return txns, nil }
func TestGetOldOwnedTransactions(t *testing.T) { mv := setupMasterVisor() up := mv.Unconfirmed // Setup txns notOursNew, err := makeValidTxn(mv) assert.Nil(t, err) notOursOld, err := makeValidTxn(mv) assert.Nil(t, err) ourSpendNew, err := makeValidTxn(mv) assert.Nil(t, err) ourSpendOld, err := makeValidTxn(mv) assert.Nil(t, err) ourReceiveNew, err := makeValidTxn(mv) assert.Nil(t, err) ourReceiveOld, err := makeValidTxn(mv) assert.Nil(t, err) ourBothNew, err := makeValidTxn(mv) assert.Nil(t, err) ourBothOld, err := makeValidTxn(mv) assert.Nil(t, err) // Add a transaction that is not ours, both new and old err, known := up.RecordTxn(mv.blockchain, notOursNew, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) up.SetAnnounced(notOursNew.Hash(), util.Now()) err, known = up.RecordTxn(mv.blockchain, notOursOld, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) // Add a transaction that is our spend, both new and old addrs := make(map[coin.Address]byte, 1) ux, ok := mv.blockchain.Unspent.Get(ourSpendNew.In[0]) assert.True(t, ok) addrs[ux.Body.Address] = byte(1) err, known = up.RecordTxn(mv.blockchain, ourSpendNew, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) up.SetAnnounced(ourSpendNew.Hash(), util.Now()) addrs = make(map[coin.Address]byte, 1) ux, ok = mv.blockchain.Unspent.Get(ourSpendNew.In[0]) assert.True(t, ok) addrs[ux.Body.Address] = byte(1) err, known = up.RecordTxn(mv.blockchain, ourSpendOld, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) // Add a transaction that is our receive, both new and old addrs = make(map[coin.Address]byte, 1) addrs[ourReceiveNew.Out[1].Address] = byte(1) err, known = up.RecordTxn(mv.blockchain, ourReceiveNew, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) up.SetAnnounced(ourReceiveNew.Hash(), util.Now()) addrs = make(map[coin.Address]byte, 1) addrs[ourReceiveOld.Out[1].Address] = byte(1) err, known = up.RecordTxn(mv.blockchain, ourReceiveOld, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) // Add a transaction that is both our spend and receive, both new and old addrs = make(map[coin.Address]byte, 2) ux, ok = mv.blockchain.Unspent.Get(ourBothNew.In[0]) assert.True(t, ok) addrs[ux.Body.Address] = byte(1) addrs[ourBothNew.Out[1].Address] = byte(1) assert.Equal(t, len(addrs), 2) err, known = up.RecordTxn(mv.blockchain, ourBothNew, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) up.SetAnnounced(ourBothNew.Hash(), util.Now()) addrs = make(map[coin.Address]byte, 1) ux, ok = mv.blockchain.Unspent.Get(ourBothOld.In[0]) assert.True(t, ok) addrs[ux.Body.Address] = byte(1) addrs[ourBothOld.Out[1].Address] = byte(1) assert.Equal(t, len(addrs), 2) err, known = up.RecordTxn(mv.blockchain, ourBothOld, addrs, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) // Get the old owned txns utxns := up.GetOldOwnedTransactions(time.Hour) // Check that the 3 txns are ones we are interested in and old enough assert.Equal(t, len(utxns), 3) mapTxns := make(map[coin.SHA256]bool) txns := make(coin.Transactions, len(utxns)) for i, utx := range utxns { txns[i] = utx.Txn assert.True(t, utx.IsOurSpend || utx.IsOurReceive) assert.True(t, utx.Announced.IsZero()) mapTxns[utx.Hash()] = true } assert.Equal(t, len(mapTxns), 3) txns = coin.SortTransactions(txns, getFee) expectTxns := coin.Transactions{ourSpendOld, ourReceiveOld, ourBothOld} expectTxns = coin.SortTransactions(expectTxns, getFee) assert.Equal(t, txns, expectTxns) }
func TestProcessTransactions(t *testing.T) { ft := FakeTree{} bc := NewBlockchain(&ft, nil) bc.CreateGenesisBlock(genAddress, _genCoins, _genTime) fmt.Println("genesis time:", bc.GetGenesisBlock().Time()) assert.Equal(t, bc.Len(), uint64(1)) _, ux := addBlockToBlockchain(t, bc) assert.Equal(t, bc.Len(), uint64(3)) // Invalid, no transactions in block // arbitrating=false txns, err := bc.processTransactions(coin.Transactions{}, false) assert.Nil(t, txns) assertError(t, err, "No transactions") // arbitrating=true txns, err = bc.processTransactions(coin.Transactions{}, true) assert.Equal(t, len(txns), 0) assert.Nil(t, err) // Invalid, txn.Verify() fails // TODO -- combine all txn.Verify() failures into one test // method, and call it from here, from ExecuteBlock(), from // Verify(), from VerifyTransaction() txns = coin.Transactions{} txn := coin.Transaction{} txn.PushInput(ux.Hash()) txn.PushOutput(genAddress, 777, 100) txn.SignInputs([]cipher.SecKey{genSecret}) txn.UpdateHeader() txns = append(txns, txn) // arbitrating=false txns2, err := bc.processTransactions(txns, false) assert.Nil(t, txns2) assertError(t, err, "Transaction outputs must be multiple of 1e6 base units") // arbitrating=true txns2, err = bc.processTransactions(txns, true) assert.NotNil(t, txns2) assert.Nil(t, err) assert.Equal(t, len(txns2), 0) // Invalid, duplicate unspent will be created by these txns txn, _ = makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, 100) txns = coin.Transactions{txn, txn} // arbitrating=false txns2, err = bc.processTransactions(txns, false) assertError(t, err, "Duplicate unspent output across transactions") assert.Nil(t, txns2) // arbitrating=true. One of the offending transactions should be removed txns2, err = bc.processTransactions(txns, true) assert.Nil(t, err) assert.Equal(t, len(txns2), 1) assert.Equal(t, txns2[0], txn) // Check that a new output will not collide with the existing pool txn, _ = makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, 100) txns = coin.Transactions{txn} uxb := coin.UxBody{ SrcTransaction: txn.Hash(), Coins: txn.Out[0].Coins, Hours: txn.Out[0].Hours, Address: txn.Out[0].Address, } bc.GetUnspent().Add(coin.UxOut{Body: uxb}) // arbitrating=false txns2, err = bc.processTransactions(txns, false) assertError(t, err, "New unspent collides with existing unspent") assert.Nil(t, txns2) // arbitrating=true txns2, err = bc.processTransactions(txns, true) assert.Equal(t, len(txns2), 0) assert.NotNil(t, txns2) assert.Nil(t, err) // Spending of duplicate inputs being spent across txns txn, _ = makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, 100) txn2, _ := makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, 100) txn2.Out = nil txn2.PushOutput(makeAddress(), 1e6, 100) txn2.PushOutput(makeAddress(), ux.Body.Coins-1e6, 100) txn2.Sigs = nil txn2.SignInputs([]cipher.SecKey{genSecret}) txn2.UpdateHeader() txns = coin.SortTransactions(coin.Transactions{txn, txn2}, bc.TransactionFee) // arbitrating=false txns2, err = bc.processTransactions(txns, false) assertError(t, err, "Cannot spend output twice in the same block") assert.Nil(t, txns2) // arbitrating=true txns2, err = bc.processTransactions(txns, true) assert.Nil(t, err) assert.Equal(t, len(txns2), 1) assert.Equal(t, txns2[0], txns[0]) }
func TestNewBlockFromTransactions(t *testing.T) { ft := FakeTree{} bc := NewBlockchain(&ft, nil) gb := bc.CreateGenesisBlock(genAddress, _genCoins, _genTime) // gb.Head.Version = 0x0F // bc.Blocks[0] = gb // assert.Equal(t, bc.GetGenesisBlock().Head.Version, uint32(0x0F)) assert.Equal(t, bc.Len(), uint64(1)) _, ux := addBlockToBlockchain(t, bc) assert.Equal(t, bc.Len(), uint64(3)) // No transactions _, err := bc.NewBlockFromTransactions(coin.Transactions{}, bc.Time()+_incTime) assertError(t, err, "No transactions") assert.Equal(t, bc.Len(), uint64(3)) // Bad currentTime, must be greater than head time fee := uint64(100) txn, _ := makeTransactionForChainWithHoursFee(t, bc, ux, genSecret, 100, fee) txns := coin.Transactions{txn} assert.Panics(t, func() { bc.NewBlockFromTransactions(txns, bc.Time()) }) // Valid transaction hrs := ux.CoinHours(bc.Time()) seq := bc.Head().Head.BkSeq b, err := bc.NewBlockFromTransactions(txns, bc.Time()+_incTime) assert.Nil(t, err) assert.Equal(t, len(b.Body.Transactions), 1) assert.Equal(t, b.Body.Transactions[0], txn) assert.Equal(t, b.Head.BkSeq, seq+1) assert.Equal(t, b.Head.Time, bc.Time()+_incTime) assert.Equal(t, b.Head.Version, gb.Head.Version) assert.Equal(t, b.Head.Fee, fee) assert.Equal(t, b.Head.Fee, hrs-txn.OutputHours()) assert.NotEqual(t, b.Head.Fee, uint64(0)) // Invalid transaction txn.InnerHash = cipher.SHA256{} txns = coin.Transactions{txn} _, err = bc.NewBlockFromTransactions(txns, bc.Time()+_incTime) assertError(t, err, "Invalid header hash") // Multiple transactions, sorted // First, split our genesis block into two, so we can make 2 valid txns uxs := splitUnspent(t, bc, ux) // tNow, make two valid txns txn = coin.Transaction{} txn.PushInput(uxs[0].Hash()) txn.PushOutput(genAddress, uxs[0].Body.Coins, uxs[0].Body.Hours) txn.SignInputs([]cipher.SecKey{genSecret}) txn.UpdateHeader() txn2 := coin.Transaction{} txn2.PushInput(uxs[1].Hash()) txn2.PushOutput(genAddress, uxs[1].Body.Coins, uxs[1].Body.Hours) txn2.SignInputs([]cipher.SecKey{genSecret}) txn2.UpdateHeader() // Combine them and sort txns = coin.Transactions{txn, txn2} txns = coin.SortTransactions(txns, bc.TransactionFee) b, err = bc.NewBlockFromTransactions(txns, bc.Time()+_incTime) assert.Nil(t, err) assert.Equal(t, len(b.Body.Transactions), 2) assert.Equal(t, b.Body.Transactions, txns) // Order should be preserved txns2 := coin.Transactions{txn, txn2} sTxns := coin.NewSortableTransactions(txns2, bc.TransactionFee) if sTxns.IsSorted() { txns2[0], txns2[1] = txns2[1], txns2[0] } b, err = bc.NewBlockFromTransactions(txns2, bc.Time()+_incTime) assert.Nil(t, err) assert.Equal(t, len(b.Body.Transactions), 2) assert.Equal(t, b.Body.Transactions, txns2) }
func TestExecuteBlock(t *testing.T) { ft := FakeTree{} bc := NewBlockchain(&ft, nil) bc.CreateGenesisBlock(genAddress, _genCoins, _genTime) assert.Equal(t, bc.Len(), uint64(1)) _, ux := addBlockToBlockchain(t, bc) assert.Equal(t, bc.Len(), uint64(3)) // Invalid block returns error b := coin.Block{} uxs, err := bc.ExecuteBlock(&b) assert.NotNil(t, err) assert.Nil(t, uxs) // Valid block, spends are removed from the unspent pool, new ones are // added. Blocks is updated, and new unspents are returns assert.Equal(t, bc.Len(), uint64(3)) assert.Equal(t, len(bc.GetUnspent().Pool), 2) spuxs := splitUnspent(t, bc, ux) tx := coin.Transaction{} tx.PushInput(spuxs[0].Hash()) coins := spuxs[0].Body.Coins extra := coins % 4e6 coins = (coins - extra) / 4 tx.PushOutput(genAddress, coins+extra, spuxs[0].Body.Hours/5) tx.PushOutput(genAddress, coins, spuxs[0].Body.Hours/6) tx.PushOutput(genAddress, coins, spuxs[0].Body.Hours/7) tx.PushOutput(genAddress, coins, spuxs[0].Body.Hours/8) tx.SignInputs([]cipher.SecKey{genSecret}) tx.UpdateHeader() tx2 := coin.Transaction{} tx2.PushInput(spuxs[1].Hash()) tx2.PushOutput(genAddress, spuxs[1].Body.Coins, spuxs[1].Body.Hours/10) tx2.SignInputs([]cipher.SecKey{genSecret}) tx2.UpdateHeader() txns := coin.Transactions{tx, tx2} sTxns := coin.NewSortableTransactions(txns, bc.TransactionFee) unswapped := sTxns.IsSorted() txns = coin.SortTransactions(txns, bc.TransactionFee) assert.Nil(t, bc.verifyTransactions(txns)) seq := bc.Head().Head.BkSeq b, err = bc.NewBlockFromTransactions(txns, bc.Time()+_incTime) assert.Equal(t, b.Head.BkSeq, seq+1) assert.Nil(t, err) assert.Equal(t, len(b.Body.Transactions), 2) assert.Equal(t, b.Body.Transactions, txns) uxs, err = bc.ExecuteBlock(&b) assert.Nil(t, err) assert.Equal(t, len(uxs), 5) // Check that all unspents look correct and are in the unspent pool txOuts := []coin.TransactionOutput{} if unswapped { txOuts = append(txOuts, tx.Out...) txOuts = append(txOuts, tx2.Out...) } else { txOuts = append(txOuts, tx2.Out...) txOuts = append(txOuts, tx.Out...) } for i, ux := range uxs { if unswapped { if i < len(tx.Out) { assert.Equal(t, ux.Body.SrcTransaction, tx.Hash()) } else { assert.Equal(t, ux.Body.SrcTransaction, tx2.Hash()) } } else { if i < len(tx2.Out) { assert.Equal(t, ux.Body.SrcTransaction, tx2.Hash()) } else { assert.Equal(t, ux.Body.SrcTransaction, tx.Hash()) } } assert.Equal(t, ux.Body.Address, txOuts[i].Address) assert.Equal(t, ux.Body.Coins, txOuts[i].Coins) assert.Equal(t, ux.Body.Hours, txOuts[i].Hours) assert.Equal(t, ux.Head.BkSeq, b.Head.BkSeq) assert.Equal(t, ux.Head.Time, b.Head.Time) assert.True(t, bc.GetUnspent().Has(ux.Hash())) } // Check that all spends are no longer in the pool txIns := []cipher.SHA256{} txIns = append(txIns, tx.In...) txIns = append(txIns, tx2.In...) for _, ux := range txIns { assert.False(t, bc.GetUnspent().Has(ux)) } }