// Adds a coin.Transaction to the pool, or updates an existing one's timestamps // Returns an error if txn is invalid, and whether the transaction already // existed in the pool. func (self *UnconfirmedTxnPool) RecordTxn(bc *coin.Blockchain, t coin.Transaction, addrs map[coin.Address]byte, maxSize int, burnFactor uint64) (error, bool) { if err := VerifyTransaction(bc, &t, maxSize, burnFactor); err != nil { return err, false } if err := bc.VerifyTransaction(t); err != nil { return err, false } // Update if we already have this txn ut, ok := self.Txns[t.Hash()] if ok { now := util.Now() ut.Received = now ut.Checked = now self.Txns[ut.Txn.Hash()] = ut return nil, true } // Add txn to index self.Txns[t.Hash()] = self.createUnconfirmedTxn(&bc.Unspent, t, addrs) // Add predicted unspents uxs := coin.CreateExpectedUnspents(t) for i, _ := range uxs { self.Unspent.Add(uxs[i]) } return nil, false }
func assertValidUnspent(t *testing.T, bc *coin.Blockchain, unspent *coin.UnspentPool, tx coin.Transaction) { expect := coin.CreateExpectedUnspents(tx) assert.NotEqual(t, len(expect), 0) assert.Equal(t, len(expect), len(unspent.Pool)) for _, ux := range expect { assert.True(t, unspent.Has(ux.Hash())) } }
// Remove a single txn func (self *UnconfirmedTxnPool) removeTxn(bc *coin.Blockchain, h coin.SHA256) { t, ok := self.Txns[h] if !ok { return } delete(self.Txns, h) outputs := coin.CreateExpectedUnspents(t.Txn) hashes := make([]coin.SHA256, len(outputs)) for i, _ := range outputs { hashes[i] = outputs[i].Hash() } self.Unspent.DelMultiple(hashes) }
func TestSpendsForAddresses(t *testing.T) { up := NewUnconfirmedTxnPool() unspent := coin.NewUnspentPool() addrs := make(map[coin.Address]byte, 0) n := 4 useAddrs := make([]coin.Address, n) for i, _ := range useAddrs { useAddrs[i] = makeAddress() } useAddrs[1] = useAddrs[0] for _, a := range useAddrs { addrs[a] = byte(1) } // Make confirmed transactions to add to unspent pool uxs := make(coin.UxArray, 0) for i := 0; i < n; i++ { txn := coin.Transaction{} txn.PushInput(randSHA256()) txn.PushOutput(useAddrs[i], 10e6, 1000) uxa := coin.CreateExpectedUnspents(txn) for _, ux := range uxa { unspent.Add(ux) } uxs = append(uxs, uxa...) } assert.Equal(t, len(uxs), 4) // Make unconfirmed txns that spend those unspents for i := 0; i < n; i++ { txn := coin.Transaction{} txn.PushInput(uxs[i].Hash()) txn.PushOutput(makeAddress(), 10e6, 1000) ut := UnconfirmedTxn{ Txn: txn, } up.Txns[ut.Hash()] = ut } // Now look them up assert.Equal(t, len(addrs), 3) assert.Equal(t, len(up.Txns), 4) auxs := up.SpendsForAddresses(&unspent, addrs) assert.Equal(t, len(auxs), 3) assert.Equal(t, len(auxs[useAddrs[0]]), 2) assert.Equal(t, len(auxs[useAddrs[2]]), 1) assert.Equal(t, len(auxs[useAddrs[3]]), 1) assert.Equal(t, auxs[useAddrs[0]], coin.UxArray{uxs[0], uxs[1]}) assert.Equal(t, auxs[useAddrs[2]], coin.UxArray{uxs[2]}) assert.Equal(t, auxs[useAddrs[3]], coin.UxArray{uxs[3]}) }
// Removes multiple txns at once. Slightly more efficient than a series of // single RemoveTxns func (self *UnconfirmedTxnPool) removeTxns(bc *coin.Blockchain, hashes []coin.SHA256) { uxo := make([]coin.UxOut, 0) for i, _ := range hashes { if t, ok := self.Txns[hashes[i]]; ok { delete(self.Txns, hashes[i]) uxo = append(uxo, coin.CreateExpectedUnspents(t.Txn)...) } } uxhashes := make([]coin.SHA256, len(uxo)) for i, _ := range uxo { uxhashes[i] = uxo[i].Hash() } self.Unspent.DelMultiple(uxhashes) }
func testRefresh(t *testing.T, mv *Visor, refresh func(checkPeriod, maxAge time.Duration)) { up := mv.Unconfirmed // Add a transaction that is invalid, but will not be checked yet // Add a transaction that is invalid, and will be checked and removed invalidTxUnchecked, err := makeValidTxn(mv) assert.Nil(t, err) invalidTxChecked, err := makeValidTxn(mv) assert.Nil(t, err) assert.Nil(t, invalidTxUnchecked.Verify()) assert.Nil(t, invalidTxChecked.Verify()) // Invalidate it by spending the output that this txn references invalidator, err := makeValidTxn(mv) assert.Nil(t, err) err, known := up.RecordTxn(mv.blockchain, invalidator, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) assert.Equal(t, len(up.Txns), 1) _, err = mv.CreateAndExecuteBlock() assert.Nil(t, err) assert.Equal(t, len(up.Txns), 0) assert.NotNil(t, mv.blockchain.VerifyTransaction(invalidTxUnchecked)) assert.NotNil(t, mv.blockchain.VerifyTransaction(invalidTxChecked)) invalidUtxUnchecked := UnconfirmedTxn{ Txn: invalidTxUnchecked, Received: util.Now(), Checked: util.Now(), Announced: util.ZeroTime(), } invalidUtxChecked := invalidUtxUnchecked invalidUtxChecked.Txn = invalidTxChecked invalidUtxUnchecked.Checked = util.Now().Add(time.Hour) invalidUtxChecked.Checked = util.Now().Add(-time.Hour) up.Txns[invalidUtxUnchecked.Hash()] = invalidUtxUnchecked up.Txns[invalidUtxChecked.Hash()] = invalidUtxChecked assert.Equal(t, len(up.Txns), 2) for _, ux := range coin.CreateExpectedUnspents(invalidTxUnchecked) { up.Unspent.Add(ux) } for _, ux := range coin.CreateExpectedUnspents(invalidTxChecked) { up.Unspent.Add(ux) } // Create a transaction that is valid, and will not be checked yet validTxUnchecked, err := makeValidTxn(mv) assert.Nil(t, err) // Create a transaction that is valid, and will be checked validTxChecked, err := makeValidTxn(mv) assert.Nil(t, err) // Create a transaction that is expired validTxExpired, err := makeValidTxn(mv) assert.Nil(t, err) // Add the transaction that is valid, and will not be checked yet err, known = up.RecordTxn(mv.blockchain, validTxUnchecked, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) validUtxUnchecked := up.Txns[validTxUnchecked.Hash()] validUtxUnchecked.Checked = util.Now().Add(time.Hour) up.Txns[validUtxUnchecked.Hash()] = validUtxUnchecked assert.Equal(t, len(up.Txns), 3) // Add the transaction that is valid, and will be checked err, known = up.RecordTxn(mv.blockchain, validTxChecked, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) validUtxChecked := up.Txns[validTxChecked.Hash()] validUtxChecked.Checked = util.Now().Add(-time.Hour) up.Txns[validUtxChecked.Hash()] = validUtxChecked assert.Equal(t, len(up.Txns), 4) // Add the transaction that is expired err, known = up.RecordTxn(mv.blockchain, validTxExpired, nil, testBlockSize, 0) assert.Nil(t, err) assert.False(t, known) validUtxExpired := up.Txns[validTxExpired.Hash()] validUtxExpired.Received = util.Now().Add(-time.Hour) up.Txns[validTxExpired.Hash()] = validUtxExpired assert.Equal(t, len(up.Txns), 5) // Pre-sanity check assert.Equal(t, len(up.Unspent.Pool), 2*5) assert.Equal(t, len(up.Txns), 5) // Refresh checkPeriod := time.Second * 2 maxAge := time.Second * 4 refresh(checkPeriod, maxAge) // All utxns that are unchecked should be exactly the same assert.Equal(t, up.Txns[validUtxUnchecked.Hash()], validUtxUnchecked) assert.Equal(t, up.Txns[invalidUtxUnchecked.Hash()], invalidUtxUnchecked) // The valid one that is checked should have its checked status updated validUtxCheckedUpdated := up.Txns[validUtxChecked.Hash()] assert.True(t, validUtxCheckedUpdated.Checked.After(validUtxChecked.Checked)) validUtxChecked.Checked = validUtxCheckedUpdated.Checked assert.Equal(t, validUtxChecked, validUtxCheckedUpdated) // The invalid checked one and the expired one should be removed _, ok := up.Txns[invalidUtxChecked.Hash()] assert.False(t, ok) _, ok = up.Txns[validUtxExpired.Hash()] assert.False(t, ok) // Also, the unspents should have 2 * nRemaining assert.Equal(t, len(up.Unspent.Pool), 2*3) assert.Equal(t, len(up.Txns), 3) }