// Receive waits for the response promised by the future and returns a // transaction given its hash. func (r FutureGetRawTransactionResult) Receive() (*dcrutil.Tx, error) { res, err := receiveFuture(r) if err != nil { return nil, err } // Unmarshal result as a string. var txHex string err = json.Unmarshal(res, &txHex) if err != nil { return nil, err } // Decode the serialized transaction hex to raw bytes. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, err } // Deserialize the transaction and return it. var msgTx wire.MsgTx if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil { return nil, err } return dcrutil.NewTx(&msgTx), nil }
// deserializeSStxRecord deserializes the passed serialized tx record information. func deserializeSStxRecord(serializedSStxRecord []byte) (*sstxRecord, error) { record := new(sstxRecord) curPos := 0 // Read MsgTx size (as a uint64). msgTxLen := int(byteOrder.Uint64( serializedSStxRecord[curPos : curPos+int64Size])) curPos += int64Size // Pretend to read the pkScrLoc for the 0th output pkScript. curPos += int32Size // Read the intended voteBits and extended voteBits length (uint8). record.voteBitsSet = false voteBitsLen := uint8(serializedSStxRecord[curPos]) if voteBitsLen != 0 { record.voteBitsSet = true } curPos += int8Size // Read the assumed 2 byte VoteBits as well as the extended // votebits (75 bytes max). record.voteBits = byteOrder.Uint16( serializedSStxRecord[curPos : curPos+int16Size]) curPos += int16Size record.voteBitsExt = make([]byte, int(voteBitsLen)-int16Size) copy(record.voteBitsExt[:], serializedSStxRecord[curPos:curPos+int(voteBitsLen)-int16Size]) curPos += stake.MaxSingleBytePushLength - int16Size // Prepare a buffer for the msgTx. buf := bytes.NewBuffer(serializedSStxRecord[curPos : curPos+msgTxLen]) curPos += msgTxLen // Deserialize transaction. msgTx := new(wire.MsgTx) err := msgTx.Deserialize(buf) if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } // Create and save the dcrutil.Tx of the read MsgTx and set its index. tx := dcrutil.NewTx((*wire.MsgTx)(msgTx)) tx.SetIndex(dcrutil.TxIndexUnknown) tx.SetTree(dcrutil.TxTreeStake) record.tx = tx // Read received unix time (int64). received := int64(byteOrder.Uint64( serializedSStxRecord[curPos : curPos+int64Size])) curPos += int64Size record.ts = time.Unix(received, 0) return record, nil }
// TestTx tests the API for Tx. func TestTx(t *testing.T) { testTx := Block100000.Transactions[0] tx := dcrutil.NewTx(testTx) // Ensure we get the same data back out. if msgTx := tx.MsgTx(); !reflect.DeepEqual(msgTx, testTx) { t.Errorf("MsgTx: mismatched MsgTx - got %v, want %v", spew.Sdump(msgTx), spew.Sdump(testTx)) } // Ensure transaction index set and get work properly. wantIndex := 0 tx.SetIndex(0) if gotIndex := tx.Index(); gotIndex != wantIndex { t.Errorf("Index: mismatched index - got %v, want %v", gotIndex, wantIndex) } // Ensure tree type set and get work properly. wantTree := int8(0) tx.SetTree(0) if gotTree := tx.Tree(); gotTree != wantTree { t.Errorf("Index: mismatched index - got %v, want %v", gotTree, wantTree) } // Ensure stake transaction index set and get work properly. wantIndex = 0 tx.SetIndex(0) if gotIndex := tx.Index(); gotIndex != wantIndex { t.Errorf("Index: mismatched index - got %v, want %v", gotIndex, wantIndex) } // Ensure tree type set and get work properly. wantTree = int8(1) tx.SetTree(1) if gotTree := tx.Tree(); gotTree != wantTree { t.Errorf("Index: mismatched index - got %v, want %v", gotTree, wantTree) } // Hash for block 100,000 transaction 0. wantHashStr := "1cbd9fe1a143a265cc819ff9d8132a7cbc4ca48eb68c0de39cfdf7ecf42cbbd1" wantHash, err := chainhash.NewHashFromStr(wantHashStr) if err != nil { t.Errorf("NewHashFromStr: %v", err) } // Request the hash multiple times to test generation and caching. for i := 0; i < 2; i++ { hash := tx.Hash() if !hash.IsEqual(wantHash) { t.Errorf("Hash #%d mismatched hash - got %v, want %v", i, hash, wantHash) } } }
// fetchTxStoreMain fetches transaction data about the provided set of // transactions from the point of view of the end of the main chain. It takes // a flag which specifies whether or not fully spent transaction should be // included in the results. func fetchTxStoreMain(db database.Db, txSet map[chainhash.Hash]struct{}, includeSpent bool) TxStore { // Just return an empty store now if there are no requested hashes. txStore := make(TxStore) if len(txSet) == 0 { return txStore } // The transaction store map needs to have an entry for every requested // transaction. By default, all the transactions are marked as missing. // Each entry will be filled in with the appropriate data below. txList := make([]*chainhash.Hash, 0, len(txSet)) for hash := range txSet { hashCopy := hash txStore[hash] = &TxData{Hash: &hashCopy, Err: database.ErrTxShaMissing} txList = append(txList, &hashCopy) } // Ask the database (main chain) for the list of transactions. This // will return the information from the point of view of the end of the // main chain. Choose whether or not to include fully spent // transactions depending on the passed flag. var txReplyList []*database.TxListReply if includeSpent { txReplyList = db.FetchTxByShaList(txList) } else { txReplyList = db.FetchUnSpentTxByShaList(txList) } for _, txReply := range txReplyList { // Lookup the existing results entry to modify. Skip // this reply if there is no corresponding entry in // the transaction store map which really should not happen, but // be safe. txD, ok := txStore[*txReply.Sha] if !ok { continue } // Fill in the transaction details. A copy is used here since // there is no guarantee the returned data isn't cached and // this code modifies the data. A bug caused by modifying the // cached data would likely be difficult to track down and could // cause subtle errors, so avoid the potential altogether. txD.Err = txReply.Err if txReply.Err == nil { txD.Tx = dcrutil.NewTx(txReply.Tx) txD.BlockHeight = txReply.Height txD.BlockIndex = txReply.Index txD.Spent = make([]bool, len(txReply.TxSpent)) copy(txD.Spent, txReply.TxSpent) } } return txStore }
func TestInsertsCreditsDebitsRollbacks(t *testing.T) { t.Parallel() // Create a double spend of the received blockchain transaction. dupRecvTx, err := dcrutil.NewTxFromBytesLegacy(TstRecvSerializedTx) if err != nil { t.Errorf("failed to deserialize test transaction: %v", err.Error()) return } // Switch txout amount to 1 DCR. Transaction store doesn't // validate txs, so this is fine for testing a double spend // removal. TstDupRecvAmount := int64(1e8) newDupMsgTx := dupRecvTx.MsgTx() newDupMsgTx.TxOut[0].Value = TstDupRecvAmount TstDoubleSpendTx := dcrutil.NewTx(newDupMsgTx) TstDoubleSpendSerializedTx := serializeTx(TstDoubleSpendTx) // Create a "signed" (with invalid sigs) tx that spends output 0 of // the double spend. spendingTx := wire.NewMsgTx() spendingTxIn := wire.NewTxIn(wire.NewOutPoint(TstDoubleSpendTx.Sha(), 0, dcrutil.TxTreeRegular), []byte{0, 1, 2, 3, 4}) spendingTx.AddTxIn(spendingTxIn) spendingTxOut1 := wire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9}) spendingTxOut2 := wire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14}) spendingTx.AddTxOut(spendingTxOut1) spendingTx.AddTxOut(spendingTxOut2) TstSpendingTx := dcrutil.NewTx(spendingTx) TstSpendingSerializedTx := serializeTx(TstSpendingTx) var _ = TstSpendingTx tests := []struct { name string f func(*Store) (*Store, error) bal, unc dcrutil.Amount unspents map[wire.OutPoint]struct{} unmined map[chainhash.Hash]struct{} }{ { name: "new store", f: func(s *Store) (*Store, error) { return s, nil }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{}, }, { name: "txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "insert duplicate unconfirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "confirmed txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "insert duplicate confirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback confirmed credit", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "insert confirmed double spend", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: dcrutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstDoubleSpendTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "insert unconfirmed debit", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert unconfirmed debit again", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert change (index 0)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, true) return s, err }, bal: 0, unc: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert output back to this own wallet (index 1)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 1, true) return s, err }, bal: 0, unc: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, wire.OutPoint{*TstSpendingTx.Sha(), 1, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "confirm signed tx", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstSignedTxBlockDetails) return s, err }, bal: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, wire.OutPoint{*TstSpendingTx.Sha(), 1, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback after spending tx", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height + 1) return s, err }, bal: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, wire.OutPoint{*TstSpendingTx.Sha(), 1, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback spending tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height) return s, err }, bal: 0, unc: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{*TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular}: {}, wire.OutPoint{*TstSpendingTx.Sha(), 1, dcrutil.TxTreeRegular}: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "rollback double spend tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstSpendingTx.Sha(), 0, dcrutil.TxTreeRegular): {}, *wire.NewOutPoint(TstSpendingTx.Sha(), 1, dcrutil.TxTreeRegular): {}, }, unmined: map[chainhash.Hash]struct{}{ *TstDoubleSpendTx.Sha(): {}, *TstSpendingTx.Sha(): {}, }, }, { name: "insert original recv txout", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: dcrutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstRecvTx.Sha(), 0, dcrutil.TxTreeRegular): {}, }, unmined: map[chainhash.Hash]struct{}{}, }, } s, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } for _, test := range tests { tmpStore, err := test.f(s) if err != nil { t.Fatalf("%s: got error: %v", test.name, err) } s = tmpStore bal, err := s.Balance(1, TstRecvCurrentHeight, wtxmgr.BFBalanceSpendable) if err != nil { t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err) } if bal != test.bal { t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) } unc, err := s.Balance(0, TstRecvCurrentHeight, wtxmgr.BFBalanceSpendable) if err != nil { t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err) } unc -= bal if unc != test.unc { t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) } // Check that unspent outputs match expected. unspent, err := s.UnspentOutputs() if err != nil { t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err) } for _, cred := range unspent { if _, ok := test.unspents[cred.OutPoint]; !ok { t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint) } delete(test.unspents, cred.OutPoint) } if len(test.unspents) != 0 { t.Fatalf("%s: missing expected unspent output(s)", test.name) } // Check that unmined txs match expected. unmined, err := s.UnminedTxs() if err != nil { t.Fatalf("%s: cannot load unmined transactions: %v", test.name, err) } for _, tx := range unmined { txHash := tx.TxSha() if _, ok := test.unmined[txHash]; !ok { t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash) } delete(test.unmined, txHash) } if len(test.unmined) != 0 { t.Fatalf("%s: missing expected unmined tx(s)", test.name) } } }
// makeUtxoView creates a mock unspent transaction output view by using the // transaction index in order to look up all inputs referenced by the // transactions in the block. This is sometimes needed when catching indexes up // because many of the txouts could actually already be spent however the // associated scripts are still required to index them. func makeUtxoView(dbTx database.Tx, block, parent *dcrutil.Block) (*blockchain.UtxoViewpoint, error) { view := blockchain.NewUtxoViewpoint() regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx == 0 { continue } // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for _, txIn := range tx.MsgTx().TxIn { // Skip already fetched outputs. originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } } for _, tx := range block.STransactions() { msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } return view, nil }
// TestCheckTransactionStandard tests the checkTransactionStandard API. func TestCheckTransactionStandard(t *testing.T) { // Create some dummy, but otherwise standard, data for transactions. prevOutHash, err := chainhash.NewHashFromStr("01") if err != nil { t.Fatalf("NewShaHashFromStr: unexpected error: %v", err) } dummyPrevOut := wire.OutPoint{Hash: *prevOutHash, Index: 1, Tree: 0} dummySigScript := bytes.Repeat([]byte{0x00}, 65) dummyTxIn := wire.TxIn{ PreviousOutPoint: dummyPrevOut, Sequence: wire.MaxTxInSequenceNum, ValueIn: 0, BlockHeight: 0, BlockIndex: 0, SignatureScript: dummySigScript, } addrHash := [20]byte{0x01} addr, err := dcrutil.NewAddressPubKeyHash(addrHash[:], &chaincfg.TestNetParams, chainec.ECTypeSecp256k1) if err != nil { t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err) } dummyPkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("PayToAddrScript: unexpected error: %v", err) } dummyTxOut := wire.TxOut{ Value: 100000000, // 1 BTC Version: 0, PkScript: dummyPkScript, } tests := []struct { name string tx wire.MsgTx height int64 isStandard bool code wire.RejectCode }{ { name: "Typical pay-to-pubkey-hash transaction", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: true, }, { name: "Transaction version too high", tx: wire.MsgTx{ Version: int32(wire.TxVersion + 1), TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction is not finalized", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: dummySigScript, Sequence: 0, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 300001, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: bytes.Repeat([]byte{0x00}, maxStandardTxSize+1), }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: bytes.Repeat([]byte{0x00}, maxStandardSigScriptSize+1), Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script that does more than push data", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: []byte{ txscript.OP_CHECKSIGVERIFY}, Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Valid but non standard public key script", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 100000000, PkScript: []byte{txscript.OP_TRUE}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "More than four nulldata outputs", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Dust output", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: dummyPkScript, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectDust, }, { name: "One nulldata output with 0 amount (standard)", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: true, }, } timeSource := blockchain.NewMedianTime() for _, test := range tests { // Ensure standardness is as expected. tx := dcrutil.NewTx(&test.tx) err := checkTransactionStandard(tx, stake.DetermineTxType(tx), test.height, timeSource, defaultMinRelayTxFee) if err == nil && test.isStandard { // Test passes since function returned standard for a // transaction which is intended to be standard. continue } if err == nil && !test.isStandard { t.Errorf("checkTransactionStandard (%s): standard when "+ "it should not be", test.name) continue } if err != nil && test.isStandard { t.Errorf("checkTransactionStandard (%s): nonstandard "+ "when it should not be: %v", test.name, err) continue } // Ensure error type is a TxRuleError inside of a RuleError. rerr, ok := err.(RuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, err) continue } txrerr, ok := rerr.Err.(TxRuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, rerr.Err) continue } // Ensure the reject code is the expected one. if txrerr.RejectCode != test.code { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error code - got %v, want %v", test.name, txrerr.RejectCode, test.code) continue } } }
// DebugMsgTxString dumps a verbose message containing information about the // contents of a transaction. func DebugMsgTxString(msgTx *wire.MsgTx) string { tx := dcrutil.NewTx(msgTx) isSStx, _ := stake.IsSStx(tx) isSSGen, _ := stake.IsSSGen(tx) var sstxType []bool var sstxPkhs [][]byte var sstxAmts []int64 var sstxRules [][]bool var sstxLimits [][]uint16 if isSStx { sstxType, sstxPkhs, sstxAmts, _, sstxRules, sstxLimits = stake.GetSStxStakeOutputInfo(tx) } var buffer bytes.Buffer hash := msgTx.TxSha() str := fmt.Sprintf("Transaction hash: %v, Version %v, Locktime: %v, "+ "Expiry %v\n\n", hash, msgTx.Version, msgTx.LockTime, msgTx.Expiry) buffer.WriteString(str) str = fmt.Sprintf("==INPUTS==\nNumber of inputs: %v\n\n", len(msgTx.TxIn)) buffer.WriteString(str) for i, input := range msgTx.TxIn { str = fmt.Sprintf("Input number: %v\n", i) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint hash: %v, ", input.PreviousOutPoint.Hash) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint index: %v, ", input.PreviousOutPoint.Index) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint tree: %v \n", input.PreviousOutPoint.Tree) buffer.WriteString(str) str = fmt.Sprintf("Sequence: %v \n", input.Sequence) buffer.WriteString(str) str = fmt.Sprintf("ValueIn: %v \n", input.ValueIn) buffer.WriteString(str) str = fmt.Sprintf("BlockHeight: %v \n", input.BlockHeight) buffer.WriteString(str) str = fmt.Sprintf("BlockIndex: %v \n", input.BlockIndex) buffer.WriteString(str) str = fmt.Sprintf("Raw signature script: %x \n", input.SignatureScript) buffer.WriteString(str) sigScr, _ := txscript.DisasmString(input.SignatureScript) str = fmt.Sprintf("Disasmed signature script: %v \n\n", sigScr) buffer.WriteString(str) } str = fmt.Sprintf("==OUTPUTS==\nNumber of outputs: %v\n\n", len(msgTx.TxOut)) buffer.WriteString(str) for i, output := range msgTx.TxOut { str = fmt.Sprintf("Output number: %v\n", i) buffer.WriteString(str) coins := float64(output.Value) / 1e8 str = fmt.Sprintf("Output amount: %v atoms or %v coins\n", output.Value, coins) buffer.WriteString(str) // SStx OP_RETURNs, dump pkhs and amts committed if isSStx && i != 0 && i%2 == 1 { coins := float64(sstxAmts[i/2]) / 1e8 str = fmt.Sprintf("SStx commit amount: %v atoms or %v coins\n", sstxAmts[i/2], coins) buffer.WriteString(str) str = fmt.Sprintf("SStx commit address: %x\n", sstxPkhs[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx address type is P2SH: %v\n", sstxType[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx all address types is P2SH: %v\n", sstxType) buffer.WriteString(str) str = fmt.Sprintf("Voting is fee limited: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) if sstxRules[i/2][0] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) } str = fmt.Sprintf("Revoking is fee limited: %v\n", sstxRules[i/2][1]) buffer.WriteString(str) if sstxRules[i/2][1] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][1]) buffer.WriteString(str) } } // SSGen block/block height OP_RETURN. if isSSGen && i == 0 { blkHash, blkHeight, _ := stake.GetSSGenBlockVotedOn(tx) str = fmt.Sprintf("SSGen block hash voted on: %v, height: %v\n", blkHash, blkHeight) buffer.WriteString(str) } if isSSGen && i == 1 { vb := stake.GetSSGenVoteBits(tx) str = fmt.Sprintf("SSGen vote bits: %v\n", vb) buffer.WriteString(str) } str = fmt.Sprintf("Raw script: %x \n", output.PkScript) buffer.WriteString(str) scr, _ := txscript.DisasmString(output.PkScript) str = fmt.Sprintf("Disasmed script: %v \n\n", scr) buffer.WriteString(str) } return buffer.String() }
func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey) txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // should either be one or more relevant inputs or outputs. // // TODO This function is pretty bad corruption wise, it's very easy // to corrupt the wallet if you ctrl+c while in this function. This // needs desperate refactoring. tx := dcrutil.NewTx(&rec.MsgTx) txHash := rec.Hash // Handle incoming SStx; store them in the stake manager if we own // the OP_SSTX tagged out, except if we're operating as a stake pool // server. In that case, additionally consider the first commitment // output as well. if is, _ := stake.IsSStx(&rec.MsgTx); is { // Errors don't matter here. If addrs is nil, the range below // does nothing. txOut := tx.MsgTx().TxOut[0] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addrmgrNs, addr) if err == nil { // We own the voting output pubkey or script and we're // not operating as a stake pool, so simply insert this // ticket now. if !w.stakePoolEnabled { insert = true break } else { // We are operating as a stake pool. The below // function will ONLY add the ticket into the // stake pool if it has been found within a // block. if block == nil { break } valid, errEval := w.evaluateStakePoolTicket(rec, block, addr) if valid { // Be sure to insert this into the user's stake // pool entry into the stake manager. poolTicket := &wstakemgr.PoolTicket{ Ticket: txHash, HeightTicket: uint32(block.Height), Status: wstakemgr.TSImmatureOrLive, } errUpdate := w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, addr, poolTicket) if errUpdate != nil { log.Warnf("Failed to insert stake pool "+ "user ticket: %s", err.Error()) } log.Debugf("Inserted stake pool ticket %v for user %v "+ "into the stake store database", txHash, addr) insert = true break } // Log errors if there were any. At this point the ticket // must be invalid, so insert it into the list of invalid // user tickets. if errEval != nil { log.Warnf("Ticket %v failed ticket evaluation for "+ "the stake pool: %s", rec.Hash, err.Error()) } errUpdate := w.StakeMgr.UpdateStakePoolUserInvalTickets( stakemgrNs, addr, &rec.Hash) if errUpdate != nil { log.Warnf("Failed to update pool user %v with "+ "invalid ticket %v", addr.EncodeAddress(), rec.Hash) } } } } if insert { err := w.StakeMgr.InsertSStx(stakemgrNs, tx, w.VoteBits) if err != nil { log.Errorf("Failed to insert SStx %v"+ "into the stake store.", tx.Sha()) } } } // Handle incoming SSGen; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSGen(&rec.MsgTx); is { if block != nil { txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSGen(stakemgrNs, &block.Hash, int64(block.Height), &txHash, w.VoteBits.Bits, &txInHash) } // If we're running as a stake pool, insert // the stake pool user ticket update too. if w.stakePoolEnabled { txInHeight := tx.MsgTx().TxIn[1].BlockHeight poolTicket := &wstakemgr.PoolTicket{ Ticket: txInHash, HeightTicket: txInHeight, Status: wstakemgr.TSVoted, SpentBy: txHash, HeightSpent: uint32(block.Height), } poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash) if err != nil { log.Warnf("Failed to fetch stake pool user for "+ "ticket %v (voted ticket)", txInHash) } else { err = w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, poolUser, poolTicket) if err != nil { log.Warnf("Failed to update stake pool ticket for "+ "stake pool user %s after voting", poolUser.EncodeAddress()) } else { log.Debugf("Updated voted stake pool ticket %v "+ "for user %v into the stake store database ("+ "vote hash: %v)", txInHash, poolUser, txHash) } } } } else { // If there's no associated block, it's potentially a // doublespent SSGen. Just ignore it and wait for it // to later get into a block. return nil } } // Handle incoming SSRtx; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSRtx(&rec.MsgTx); is { if block != nil { txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSRtx(stakemgrNs, &block.Hash, int64(block.Height), &txHash, &txInHash) } // If we're running as a stake pool, insert // the stake pool user ticket update too. if w.stakePoolEnabled { txInHeight := tx.MsgTx().TxIn[0].BlockHeight poolTicket := &wstakemgr.PoolTicket{ Ticket: txInHash, HeightTicket: txInHeight, Status: wstakemgr.TSMissed, SpentBy: txHash, HeightSpent: uint32(block.Height), } poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash) if err != nil { log.Warnf("failed to fetch stake pool user for "+ "ticket %v (missed ticket)", txInHash) } else { err = w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, poolUser, poolTicket) if err != nil { log.Warnf("failed to update stake pool ticket for "+ "stake pool user %s after revoking", poolUser.EncodeAddress()) } else { log.Debugf("Updated missed stake pool ticket %v "+ "for user %v into the stake store database ("+ "revocation hash: %v)", txInHash, poolUser, txHash) } } } } } err := w.TxStore.InsertTx(txmgrNs, addrmgrNs, rec, block) if err != nil { return err } // Handle input scripts that contain P2PKs that we care about. for i, input := range rec.MsgTx.TxIn { if txscript.IsMultisigSigScript(input.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( input.SignatureScript) if err != nil { return err } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } isRelevant := false for _, addr := range addrs { _, err := w.Manager.Address(addrmgrNs, addr) if err == nil { isRelevant = true err = w.Manager.MarkUsed(addrmgrNs, addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) } else { // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } // Add the script to the script databases. // TODO Markused script address? cj if isRelevant { err = w.TxStore.InsertTxScript(txmgrNs, rs) if err != nil { return err } var blockToUse *waddrmgr.BlockStamp if block != nil { blockToUse = &waddrmgr.BlockStamp{ Height: block.Height, Hash: block.Hash, } } mscriptaddr, err := w.Manager.ImportScript(addrmgrNs, rs, blockToUse) if err != nil { switch { // Don't care if it's already there. case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress): break case waddrmgr.IsError(err, waddrmgr.ErrLocked): log.Warnf("failed to attempt script importation "+ "of incoming tx script %x because addrmgr "+ "was locked", rs) break default: return err } } else { // This is the first time seeing this script address // belongs to us, so do a rescan and see if there are // any other outputs to this address. job := &RescanJob{ Addrs: []dcrutil.Address{mscriptaddr.Address()}, OutPoints: nil, BlockStamp: waddrmgr.BlockStamp{ Height: 0, Hash: *w.chainParams.GenesisHash, }, } // Submit rescan job and log when the import has completed. // Do not block on finishing the rescan. The rescan success // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) } } // If we're spending a multisig outpoint we know about, // update the outpoint. Inefficient because you deserialize // the entire multisig output info. Consider a specific // exists function in wtxmgr. The error here is skipped // because the absence of an multisignature output for // some script can not always be considered an error. For // example, the wallet might be rescanning as called from // the above function and so does not have the output // included yet. mso, err := w.TxStore.GetMultisigOutput(txmgrNs, &input.PreviousOutPoint) if mso != nil && err == nil { w.TxStore.SpendMultisigOut(txmgrNs, &input.PreviousOutPoint, rec.Hash, uint32(i)) } } } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { // Ignore unspendable outputs. if output.Value == 0 { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } isStakeType := class == txscript.StakeSubmissionTy || class == txscript.StakeSubChangeTy || class == txscript.StakeGenTy || class == txscript.StakeRevocationTy if isStakeType { class, err = txscript.GetStakeOutSubclass(output.PkScript) if err != nil { log.Errorf("Unknown stake output subclass parse error "+ "encountered: %v", err) continue } } for _, addr := range addrs { ma, err := w.Manager.Address(addrmgrNs, addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. err = w.TxStore.AddCredit(txmgrNs, rec, block, uint32(i), ma.Internal(), ma.Account()) if err != nil { return err } err = w.Manager.MarkUsed(addrmgrNs, addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } // Handle P2SH addresses that are multisignature scripts // with keys that we own. if class == txscript.ScriptHashTy { var expandedScript []byte for _, addr := range addrs { // Search both the script store in the tx store // and the address manager for the redeem script. var err error expandedScript, err = w.TxStore.GetTxScript(txmgrNs, addr.ScriptAddress()) if err != nil { return err } if expandedScript == nil { scrAddr, err := w.Manager.Address(addrmgrNs, addr) if err == nil { sa, ok := scrAddr.(waddrmgr.ManagedScriptAddress) if !ok { log.Warnf("address %v is not a script"+ " address (type %T)", scrAddr.Address().EncodeAddress(), scrAddr.Address()) continue } retrievedScript, err := sa.Script() if err != nil { log.Errorf("failed to decode redeemscript for "+ "address %v: %v", addr.EncodeAddress(), err.Error()) continue } expandedScript = retrievedScript } else { // We can't find this redeem script anywhere. // Skip this output. log.Debugf("failed to find redeemscript for "+ "address %v in address manager: %v", addr.EncodeAddress(), err.Error()) continue } } } // Otherwise, extract the actual addresses and // see if any belong to us. expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, expandedScript, w.chainParams) if err != nil { return err } // Skip non-multisig scripts. if expClass != txscript.MultiSigTy { continue } for _, maddr := range multisigAddrs { _, err := w.Manager.Address(addrmgrNs, maddr) // An address we own; handle accordingly. if err == nil { errStore := w.TxStore.AddMultisigOut( txmgrNs, rec, block, uint32(i)) if errStore != nil { // This will throw if there are multiple private keys // for this multisignature output owned by the wallet, // so it's routed to debug. log.Debugf("unable to add multisignature output: %v", errStore.Error()) } } } } } // Send notification of mined or unmined transaction to any interested // clients. // // TODO: Avoid the extra db hits. if block == nil { details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { w.NtfnServer.notifyUnminedTransaction(dbtx, details) } } else { details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { w.NtfnServer.notifyMinedTransaction(dbtx, details, block) } } return nil }
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { // TODO: The transaction store and address manager need to be updated // together, but each operate under different namespaces and are changed // under new transactions. This is not error safe as we lose // transaction semantics. // // I'm unsure of the best way to solve this. Some possible solutions // and drawbacks: // // 1. Open write transactions here and pass the handle to every // waddrmr and wtxmgr method. This complicates the caller code // everywhere, however. // // 2. Move the wtxmgr namespace into the waddrmgr namespace, likely // under its own bucket. This entire function can then be moved // into the waddrmgr package, which updates the nested wtxmgr. // This removes some of separation between the components. // // 3. Use multiple wtxmgrs, one for each account, nested in the // waddrmgr namespace. This still provides some sort of logical // separation (transaction handling remains in another package, and // is simply used by waddrmgr), but may result in duplicate // transactions being saved if they are relevant to multiple // accounts. // // 4. Store wtxmgr-related details under the waddrmgr namespace, but // solve the drawback of #3 by splitting wtxmgr to save entire // transaction records globally for all accounts, with // credit/debit/balance tracking per account. Each account would // also save the relevant transaction hashes and block incidence so // the full transaction can be loaded from the waddrmgr // transactions bucket. This currently seems like the best // solution. // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // should either be one or more relevant inputs or outputs. // // TODO This function is pretty bad corruption wise, it's very easy // to corrupt the wallet if you ctrl+c while in this function. This // needs desperate refactoring. tx := dcrutil.NewTx(&rec.MsgTx) // Handle incoming SStx; store them in the stake manager if we own // the OP_SSTX tagged out. if is, _ := stake.IsSStx(tx); is { // Errors don't matter here. If addrs is nil, the range below // does nothing. txOut := tx.MsgTx().TxOut[0] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addr) if err == nil { insert = true break } } if insert { err := w.StakeMgr.InsertSStx(tx) if err != nil { log.Errorf("Failed to insert SStx %v"+ "into the stake store.", tx.Sha()) } } } // Handle incoming SSGen; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSGen(tx); is { if block != nil { txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSGen(&block.Hash, int64(block.Height), tx.Sha(), w.VoteBits, &txInHash) } } else { // If there's no associated block, it's potentially a // doublespent SSGen. Just ignore it and wait for it // to later get into a block. return nil } } // Handle incoming SSRtx; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSRtx(tx); is { if block != nil { txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSRtx(&block.Hash, int64(block.Height), tx.Sha(), &txInHash) } } } err := w.TxStore.InsertTx(rec, block) if err != nil { return err } // Handle input scripts that contain P2PKs that we care about. for i, input := range rec.MsgTx.TxIn { if txscript.IsMultisigSigScript(input.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( input.SignatureScript) if err != nil { return err } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } isRelevant := false for _, addr := range addrs { _, err := w.Manager.Address(addr) if err == nil { isRelevant = true err = w.Manager.MarkUsed(addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) } else { // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } // Add the script to the script databases. // TODO Markused script address? cj if isRelevant { err = w.TxStore.InsertTxScript(rs) if err != nil { return err } var blockToUse *waddrmgr.BlockStamp if block != nil { blockToUse = &waddrmgr.BlockStamp{block.Height, block.Hash} } mscriptaddr, err := w.Manager.ImportScript(rs, blockToUse) if err != nil { switch { // Don't care if it's already there. case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress): break case waddrmgr.IsError(err, waddrmgr.ErrLocked): log.Debugf("failed to attempt script importation " + "of incoming tx because addrmgr was locked") break default: return err } } else { // This is the first time seeing this script address // belongs to us, so do a rescan and see if there are // any other outputs to this address. job := &RescanJob{ Addrs: []dcrutil.Address{mscriptaddr.Address()}, OutPoints: nil, BlockStamp: waddrmgr.BlockStamp{ 0, *w.chainParams.GenesisHash, }, } // Submit rescan job and log when the import has completed. // Do not block on finishing the rescan. The rescan success // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) } } // If we're spending a multisig outpoint we // know about, update the outpoint. // Inefficient because you deserialize the // entire multisig output info, consider // a specific exists function in wtxmgr. cj mso, err := w.TxStore.GetMultisigOutput(&input.PreviousOutPoint) if err != nil { return err } if mso != nil { w.TxStore.SpendMultisigOut(&input.PreviousOutPoint, rec.Hash, uint32(i)) } } } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } isStakeType := class == txscript.StakeSubmissionTy || class == txscript.StakeSubChangeTy || class == txscript.StakeGenTy || class == txscript.StakeRevocationTy if isStakeType { class, err = txscript.GetStakeOutSubclass(output.PkScript) if err != nil { log.Errorf("Unknown stake output subclass encountered") continue } } switch { case class == txscript.PubKeyHashTy: for _, addr := range addrs { ma, err := w.Manager.Address(addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. err = w.TxStore.AddCredit(rec, block, uint32(i), ma.Internal()) if err != nil { return err } err = w.Manager.MarkUsed(addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } // Handle P2SH addresses that are multisignature scripts // with keys that we own. case class == txscript.ScriptHashTy: var expandedScript []byte for _, addr := range addrs { var err error expandedScript, err = w.TxStore.GetTxScript(addr.ScriptAddress()) if err != nil { return err } // TODO make this work, the type conversion is broken cj //scrAddr, err := w.Manager.Address(addr) //if err == nil { // addrTyped := scrAddr.(*waddrmgr.ManagedScriptAddress) // retrievedScript, err := addrTyped.Script() // if err == nil { // expandedScript = retrievedScript // } //} } // We don't have the script for this hash, skip. if expandedScript == nil { continue } // Otherwise, extract the actual addresses and // see if any belong to us. expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, expandedScript, w.chainParams) if err != nil { return err } // Skip non-multisig scripts. if expClass != txscript.MultiSigTy { continue } for _, maddr := range multisigAddrs { _, err := w.Manager.Address(maddr) // An address we own; handle accordingly. if err == nil { errStore := w.TxStore.AddMultisigOut(rec, block, uint32(i)) if errStore != nil { // This will throw if there are multiple private keys // for this multisignature output owned by the wallet, // so it's routed to debug. log.Debugf("unable to add multisignature output: %v", errStore.Error()) } } } } } // TODO: Notify connected clients of the added transaction. bs, err := w.chainSvr.BlockStamp() if err == nil { w.notifyBalances(bs.Height, wtxmgr.BFBalanceSpendable) } return nil }
// GenerateRevocation generates a revocation (SSRtx), signs it, and // submits it by SendRawTransaction. It also stores a record of it // in the local database. func (s *StakeStore) generateRevocation(blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash) (*StakeNotification, error) { var revocationFee int64 switch { case s.Params == &chaincfg.MainNetParams: revocationFee = revocationFeeMainNet case s.Params == &chaincfg.TestNetParams: revocationFee = revocationFeeTestNet default: revocationFee = revocationFeeTestNet } // 1. Fetch the SStx, then calculate all the values we'll need later for // the generation of the SSRtx tx outputs. sstxRecord, err := s.getSStx(sstxHash) if err != nil { return nil, err } sstx := sstxRecord.tx // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. // TODO Get information on the allowable fee range for the revocation // and check to make sure we don't overflow that. sstxPayTypes, sstxPkhs, sstxAmts, _, _, _ := stake.GetSStxStakeOutputInfo(sstx) ssrtxCalcAmts := stake.GetStakeRewards(sstxAmts, sstx.MsgTx().TxOut[0].Value, int64(0)) // 2. Add the only input. msgTx := wire.NewMsgTx() // SStx tagged output as an OutPoint; reference this as // the only input. prevOut := wire.NewOutPoint(sstxHash, 0, // Index 0 1) // Tree stake txIn := wire.NewTxIn(prevOut, []byte{}) msgTx.AddTxIn(txIn) // 3. Add all the OP_SSRTX tagged outputs. // Add all the SSRtx-tagged transaction outputs to the transaction after // performing some validity checks. feeAdded := false for i, sstxPkh := range sstxPkhs { // Create a new script which pays to the provided address specified in // the original ticket tx. var ssrtxOutScript []byte switch sstxPayTypes[i] { case false: // P2PKH ssrtxOutScript, err = txscript.PayToSSRtxPKHDirect(sstxPkh) if err != nil { return nil, err } case true: // P2SH ssrtxOutScript, err = txscript.PayToSSRtxSHDirect(sstxPkh) if err != nil { return nil, err } } // Add a fee from an output that has enough. amt := ssrtxCalcAmts[i] if !feeAdded && ssrtxCalcAmts[i] >= revocationFee { amt -= revocationFee feeAdded = true } // Add the txout to our SSRtx tx. txOut := wire.NewTxOut(amt, ssrtxOutScript) msgTx.AddTxOut(txOut) } // Check to make sure our SSRtx was created correctly. ssrtxTx := dcrutil.NewTx(msgTx) ssrtxTx.SetTree(dcrutil.TxTreeStake) _, err = stake.IsSSRtx(ssrtxTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(msgTx, sstx, false) if err != nil { return nil, err } // Send the transaction. ssrtxSha, err := s.chainSvr.SendRawTransaction(msgTx, false) if err != nil { return nil, err } // Store the information about the SSRtx. err = s.insertSSRtx(blockHash, height, ssrtxSha, sstx.Sha()) if err != nil { return nil, err } log.Debugf("Generated SSRtx %v. "+ "The ticket used to generate the SSRtx was %v.", ssrtxSha, sstx.Sha()) // Generate a notification to return. ntfn := &StakeNotification{ TxType: int8(stake.TxTypeSSRtx), TxHash: *ssrtxSha, BlockHash: chainhash.Hash{}, Height: 0, Amount: 0, SStxIn: *sstx.Sha(), VoteBits: 0, } return ntfn, nil }
// GenerateVote creates a new SSGen given a header hash, height, sstx // tx hash, and votebits. func (s *StakeStore) generateVote(blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash, voteBits uint16) (*StakeNotification, error) { // 1. Fetch the SStx, then calculate all the values we'll need later for // the generation of the SSGen tx outputs. sstxRecord, err := s.getSStx(sstxHash) if err != nil { return nil, err } sstx := sstxRecord.tx sstxMsgTx := sstx.MsgTx() // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. // TODO Get information on the allowable fee range for the vote // and check to make sure we don't overflow that. ssgenPayTypes, ssgenPkhs, sstxAmts, _, _, _ := stake.GetSStxStakeOutputInfo(sstx) // Get the current reward. stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(height, s.Params) // Calculate the output values from this data. ssgenCalcAmts := stake.GetStakeRewards(sstxAmts, sstxMsgTx.TxOut[0].Value, stakeVoteSubsidy) // 2. Add all transaction inputs to a new transaction after performing // some validity checks. First, add the stake base, then the OP_SSTX // tagged output. msgTx := wire.NewMsgTx() // Stakebase. stakeBaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, uint32(0xFFFFFFFF), dcrutil.TxTreeRegular) txInStakeBase := wire.NewTxIn(stakeBaseOutPoint, []byte{}) msgTx.AddTxIn(txInStakeBase) // Add the subsidy amount into the input. msgTx.TxIn[0].ValueIn = stakeVoteSubsidy // SStx tagged output as an OutPoint. prevOut := wire.NewOutPoint(sstxHash, 0, // Index 0 1) // Tree stake txIn := wire.NewTxIn(prevOut, []byte{}) msgTx.AddTxIn(txIn) // 3. Add the OP_RETURN null data pushes of the block header hash, // the block height, and votebits, then add all the OP_SSGEN tagged // outputs. // // Block reference output. blockRefScript, err := txscript.GenerateSSGenBlockRef(*blockHash, uint32(height)) if err != nil { return nil, err } blockRefOut := wire.NewTxOut(0, blockRefScript) msgTx.AddTxOut(blockRefOut) // Votebits output. blockVBScript, err := txscript.GenerateSSGenVotes(voteBits) if err != nil { return nil, err } blockVBOut := wire.NewTxOut(0, blockVBScript) msgTx.AddTxOut(blockVBOut) // Add all the SSGen-tagged transaction outputs to the transaction after // performing some validity checks. for i, ssgenPkh := range ssgenPkhs { // Create a new script which pays to the provided address specified in // the original ticket tx. var ssgenOutScript []byte switch ssgenPayTypes[i] { case false: // P2PKH ssgenOutScript, err = txscript.PayToSSGenPKHDirect(ssgenPkh) if err != nil { return nil, err } case true: // P2SH ssgenOutScript, err = txscript.PayToSSGenSHDirect(ssgenPkh) if err != nil { return nil, err } } // Add the txout to our SSGen tx. txOut := wire.NewTxOut(ssgenCalcAmts[i], ssgenOutScript) msgTx.AddTxOut(txOut) } // Check to make sure our SSGen was created correctly. ssgenTx := dcrutil.NewTx(msgTx) ssgenTx.SetTree(dcrutil.TxTreeStake) _, err = stake.IsSSGen(ssgenTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(msgTx, sstx, true) if err != nil { return nil, err } // Send the transaction. ssgenSha, err := s.chainSvr.SendRawTransaction(msgTx, false) if err != nil { return nil, err } // Store the information about the SSGen. err = s.insertSSGen(blockHash, height, ssgenSha, voteBits, sstx.Sha()) if err != nil { return nil, err } log.Debugf("Generated SSGen %v , voting on block %v at height %v. "+ "The ticket used to generate the SSGen was %v.", ssgenSha, blockHash, height, sstxHash) // Generate a notification to return. ntfn := &StakeNotification{ TxType: int8(stake.TxTypeSSGen), TxHash: *ssgenSha, BlockHash: *blockHash, Height: int32(height), Amount: 0, SStxIn: *sstx.Sha(), VoteBits: voteBits, } return ntfn, nil }
// loadTxStore returns a transaction store loaded from a file. func loadTxStore(filename string) (blockchain.TxStore, error) { // The txstore file format is: // <num tx data entries> <tx length> <serialized tx> <blk height> // <num spent bits> <spent bits> // // All num and length fields are little-endian uint32s. The spent bits // field is padded to a byte boundary. filename = filepath.Join("testdata/", filename) fi, err := os.Open(filename) if err != nil { return nil, err } // Choose read based on whether the file is compressed or not. var r io.Reader if strings.HasSuffix(filename, ".bz2") { r = bzip2.NewReader(fi) } else { r = fi } defer fi.Close() // Num of transaction store objects. var numItems uint32 if err := binary.Read(r, binary.LittleEndian, &numItems); err != nil { return nil, err } txStore := make(blockchain.TxStore) var uintBuf uint32 for height := uint32(0); height < numItems; height++ { txD := blockchain.TxData{} // Serialized transaction length. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } serializedTxLen := uintBuf if serializedTxLen > wire.MaxBlockPayload { return nil, fmt.Errorf("Read serialized transaction "+ "length of %d is larger max allowed %d", serializedTxLen, wire.MaxBlockPayload) } // Transaction. var msgTx wire.MsgTx err = msgTx.Deserialize(r) if err != nil { return nil, err } txD.Tx = dcrutil.NewTx(&msgTx) // Transaction hash. txHash := msgTx.TxSha() txD.Hash = &txHash // Block height the transaction came from. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } txD.BlockHeight = int64(uintBuf) // Num spent bits. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } numSpentBits := uintBuf numSpentBytes := numSpentBits / 8 if numSpentBits%8 != 0 { numSpentBytes++ } // Packed spent bytes. spentBytes := make([]byte, numSpentBytes) _, err = io.ReadFull(r, spentBytes) if err != nil { return nil, err } // Populate spent data based on spent bits. txD.Spent = make([]bool, numSpentBits) for byteNum, spentByte := range spentBytes { for bit := 0; bit < 8; bit++ { if uint32((byteNum*8)+bit) < numSpentBits { if spentByte&(1<<uint(bit)) != 0 { txD.Spent[(byteNum*8)+bit] = true } } } } txStore[*txD.Hash] = &txD } return txStore, nil }