// TestTxSerializeSize performs tests to ensure the serialize size for various // transactions is accurate. func TestTxSerializeSize(t *testing.T) { // Empty tx message. noTx := wire.NewMsgTx() noTx.Version = 1 tests := []struct { in *wire.MsgTx // Tx to encode size int // Expected serialized size }{ // No inputs or outpus. {noTx, 15}, // Transaction with an input and an output. {multiTx, 236}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { serializedSize := test.in.SerializeSize() if serializedSize != test.size { t.Errorf("MsgTx.SerializeSize: #%d got: %d, want: %d", i, serializedSize, test.size) continue } } }
// createSpendTx generates a basic spending transaction given the passed // signature and public key scripts. func createSpendingTx(sigScript, pkScript []byte) *wire.MsgTx { coinbaseTx := wire.NewMsgTx() outPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0), dcrutil.TxTreeRegular) txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}) txOut := wire.NewTxOut(0, pkScript) coinbaseTx.AddTxIn(txIn) coinbaseTx.AddTxOut(txOut) spendingTx := wire.NewMsgTx() coinbaseTxSha := coinbaseTx.TxSha() outPoint = wire.NewOutPoint(&coinbaseTxSha, 0, dcrutil.TxTreeRegular) txIn = wire.NewTxIn(outPoint, sigScript) txOut = wire.NewTxOut(0, nil) spendingTx.AddTxIn(txIn) spendingTx.AddTxOut(txOut) return spendingTx }
// TestTxSha tests the ability to generate the hash of a transaction accurately. func TestTxSha(t *testing.T) { // Hash of first transaction from block 113875. hashStr := "4538fc1618badd058ee88fd020984451024858796be0a1ed111877f887e1bd53" wantHash, err := chainhash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) return } msgTx := wire.NewMsgTx() txIn := wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: chainhash.Hash{}, Index: 0xffffffff, Tree: dcrutil.TxTreeRegular, }, Sequence: 0xffffffff, ValueIn: 5000000000, BlockHeight: 0x3F3F3F3F, BlockIndex: 0x2E2E2E2E, SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}, } txOut := wire.TxOut{ Value: 5000000000, Version: 0xF0F0, PkScript: []byte{ 0x41, // OP_DATA_65 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, 0xa6, // 65-byte signature 0xac, // OP_CHECKSIG }, } msgTx.AddTxIn(&txIn) msgTx.AddTxOut(&txOut) msgTx.LockTime = 0 msgTx.Expiry = 0 // Ensure the hash produced is expected. txHash := msgTx.TxSha() if !txHash.IsEqual(wantHash) { t.Errorf("TxSha: wrong hash - got %v, want %v", spew.Sprint(txHash), spew.Sprint(wantHash)) } }
// toMsgTx generates a wire.MsgTx with this tx's inputs and outputs. func (tx *withdrawalTx) toMsgTx() *wire.MsgTx { msgtx := wire.NewMsgTx() for _, o := range tx.outputs { msgtx.AddTxOut(wire.NewTxOut(int64(o.amount), o.pkScript())) } if tx.hasChange() { msgtx.AddTxOut(tx.changeOutput) } for _, i := range tx.inputs { msgtx.AddTxIn(wire.NewTxIn(&i.OutPoint, []byte{})) } return msgtx }
func Test_addOutputs(t *testing.T) { msgtx := wire.NewMsgTx() pairs := map[string]dcrutil.Amount{outAddr1: 10, outAddr2: 1} if _, err := addOutputs(msgtx, pairs, &chaincfg.TestNetParams); err != nil { t.Fatal(err) } if len(msgtx.TxOut) != 2 { t.Fatalf("Expected 2 outputs, found only %d", len(msgtx.TxOut)) } values := []int{int(msgtx.TxOut[0].Value), int(msgtx.TxOut[1].Value)} sort.Ints(values) if !reflect.DeepEqual(values, []int{1, 10}) { t.Fatalf("Expected values to be [1, 10], got: %v", values) } }
// NewMsgTxWithInputCoins takes the coins in the CoinSet and makes them // the inputs to a new wire.MsgTx which is returned. func NewMsgTxWithInputCoins(inputCoins Coins) *wire.MsgTx { msgTx := wire.NewMsgTx() coins := inputCoins.Coins() msgTx.TxIn = make([]*wire.TxIn, len(coins)) for i, coin := range coins { msgTx.TxIn[i] = &wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: *coin.Hash(), Index: coin.Index(), }, SignatureScript: nil, Sequence: wire.MaxTxInSequenceNum, } } return msgTx }
// makeTx generates a transaction spending outputs to a single address. func makeTx(params *chaincfg.Params, inputs []*extendedOutPoint, addr dcrutil.Address, txFee int64) (*wire.MsgTx, error) { mtx := wire.NewMsgTx() allInAmts := int64(0) for _, input := range inputs { txIn := wire.NewTxIn(input.op, []byte{}) mtx.AddTxIn(txIn) allInAmts += input.amt } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } txOut := wire.NewTxOut(allInAmts-txFee, pkScript) txOut.Version = txscript.DefaultScriptVersion mtx.AddTxOut(txOut) return mtx, 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(ns walletdb.ReadWriteBucket, waddrmgrNs walletdb.ReadBucket, blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash, allowHighFees bool) (*StakeNotification, error) { // 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(ns, 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.TxSStxStakeOutputInfo(sstx.MsgTx()) ssrtxCalcAmts := stake.CalculateRewards(sstxAmts, sstx.MsgTx().TxOut[0].Value, int64(0)) // Calculate the fee to use for this revocation based on the fee // per KB that is standard for mainnet. revocationSizeEst := estimateSSRtxTxSize(1, len(sstxPkhs)) revocationFee := txrules.FeeForSerializeSize(revocationFeePerKB, revocationSizeEst) // 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] >= int64(revocationFee) { amt -= int64(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. _, err = stake.IsSSRtx(msgTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(waddrmgrNs, msgTx, sstx, false) if err != nil { return nil, err } // Store the information about the SSRtx. hash := msgTx.TxSha() err = s.insertSSRtx(ns, blockHash, height, &hash, sstx.Sha()) if err != nil { return nil, err } // Send the transaction. ssrtxSha, err := s.chainSvr.SendRawTransaction(msgTx, allowHighFees) 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(ns walletdb.ReadWriteBucket, waddrmgrNs walletdb.ReadBucket, blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash, defaultVoteBits stake.VoteBits, allowHighFees bool) (*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(ns, sstxHash) if err != nil { return nil, err } sstx := sstxRecord.tx sstxMsgTx := sstx.MsgTx() // The legacy wallet didn't store anything about the voteBits to use. // In the case we're loading a legacy wallet and the voteBits are // unset, just use the default voteBits as set by the user. voteBits := defaultVoteBits if sstxRecord.voteBitsSet { voteBits.Bits = sstxRecord.voteBits voteBits.ExtendedBits = sstxRecord.voteBitsExt } // 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.TxSStxStakeOutputInfo(sstxMsgTx) // Get the current reward. initSudsidyCacheOnce.Do(func() { subsidyCache = blockchain.NewSubsidyCache(height, s.Params) }) stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, height, s.Params) // Calculate the output values from this data. ssgenCalcAmts := stake.CalculateRewards(sstxAmts, sstxMsgTx.TxOut[0].Value, stakeVoteSubsidy) subsidyCache = blockchain.NewSubsidyCache(height, s.Params) // 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), wire.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 := generateVoteScript(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. _, err = stake.IsSSGen(msgTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(waddrmgrNs, msgTx, sstx, true) if err != nil { return nil, err } // Store the information about the SSGen. hash := msgTx.TxSha() err = s.insertSSGen(ns, blockHash, height, &hash, voteBits.Bits, sstx.Sha()) if err != nil { return nil, err } // Send the transaction. ssgenSha, err := s.chainSvr.SendRawTransaction(msgTx, allowHighFees) 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.Bits, } return ntfn, nil }
func TestLimitAndSkipFetchTxsForAddr(t *testing.T) { testDb, err := setUpTestDb(t, "tstdbtxaddr") if err != nil { t.Errorf("Failed to open test database %v", err) return } defer testDb.cleanUpFunc() _, err = testDb.db.InsertBlock(testDb.blocks[0]) if err != nil { t.Fatalf("failed to insert initial block") } // Insert a block with some fake test transactions. The block will have // 10 copies of a fake transaction involving same address. addrString := "DsZEAobx6qJ7K2qaHZBA2vBn66Nor8KYAKk" targetAddr, err := dcrutil.DecodeAddress(addrString, &chaincfg.MainNetParams) if err != nil { t.Fatalf("Unable to decode test address: %v", err) } outputScript, err := txscript.PayToAddrScript(targetAddr) if err != nil { t.Fatalf("Unable make test pkScript %v", err) } fakeTxOut := wire.NewTxOut(10, outputScript) var emptyHash chainhash.Hash fakeHeader := wire.NewBlockHeader(0, &emptyHash, &emptyHash, &emptyHash, 1, [6]byte{}, 1, 1, 1, 1, 1, 1, 1, 1, 1, [36]byte{}) msgBlock := wire.NewMsgBlock(fakeHeader) for i := 0; i < 10; i++ { mtx := wire.NewMsgTx() mtx.AddTxOut(fakeTxOut) msgBlock.AddTransaction(mtx) } lastBlock := testDb.blocks[0] msgBlock.Header.PrevBlock = *lastBlock.Sha() // Insert the test block into the DB. testBlock := dcrutil.NewBlock(msgBlock) newheight, err := testDb.db.InsertBlock(testBlock) if err != nil { t.Fatalf("Unable to insert block into db: %v", err) } // Create and insert an address index for out test addr. txLoc, _, _ := testBlock.TxLoc() index := make(database.BlockAddrIndex, len(txLoc)) for i := range testBlock.Transactions() { var hash160 [ripemd160.Size]byte scriptAddr := targetAddr.ScriptAddress() copy(hash160[:], scriptAddr[:]) txAddrIndex := &database.TxAddrIndex{ Hash160: hash160, Height: uint32(newheight), TxOffset: uint32(txLoc[i].TxStart), TxLen: uint32(txLoc[i].TxLen), } index[i] = txAddrIndex } blkSha := testBlock.Sha() err = testDb.db.UpdateAddrIndexForBlock(blkSha, newheight, index) if err != nil { t.Fatalf("UpdateAddrIndexForBlock: failed to index"+ " addrs for block #%d (%s) "+ "err %v", newheight, blkSha, err) return } // Try skipping the first 4 results, should get 6 in return. txReply, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 6 { t.Fatalf("Did not correctly skip forward in txs for address reply"+ " got %v txs, expected %v", len(txReply), 6) } // Limit the number of results to 3. txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 3 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 3) } // Skip 1, limit 5. txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 5 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 5) } }
// TestPeerListeners tests that the peer listeners are called as expected. func TestPeerListeners(t *testing.T) { verack := make(chan struct{}, 1) ok := make(chan wire.Message, 20) peerCfg := &peer.Config{ Listeners: peer.MessageListeners{ OnGetAddr: func(p *peer.Peer, msg *wire.MsgGetAddr) { ok <- msg }, OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) { ok <- msg }, OnPing: func(p *peer.Peer, msg *wire.MsgPing) { ok <- msg }, OnPong: func(p *peer.Peer, msg *wire.MsgPong) { ok <- msg }, OnAlert: func(p *peer.Peer, msg *wire.MsgAlert) { ok <- msg }, OnMemPool: func(p *peer.Peer, msg *wire.MsgMemPool) { ok <- msg }, OnTx: func(p *peer.Peer, msg *wire.MsgTx) { ok <- msg }, OnBlock: func(p *peer.Peer, msg *wire.MsgBlock, buf []byte) { ok <- msg }, OnInv: func(p *peer.Peer, msg *wire.MsgInv) { ok <- msg }, OnHeaders: func(p *peer.Peer, msg *wire.MsgHeaders) { ok <- msg }, OnNotFound: func(p *peer.Peer, msg *wire.MsgNotFound) { ok <- msg }, OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { ok <- msg }, OnGetBlocks: func(p *peer.Peer, msg *wire.MsgGetBlocks) { ok <- msg }, OnGetHeaders: func(p *peer.Peer, msg *wire.MsgGetHeaders) { ok <- msg }, OnFilterAdd: func(p *peer.Peer, msg *wire.MsgFilterAdd) { ok <- msg }, OnFilterClear: func(p *peer.Peer, msg *wire.MsgFilterClear) { ok <- msg }, OnFilterLoad: func(p *peer.Peer, msg *wire.MsgFilterLoad) { ok <- msg }, OnMerkleBlock: func(p *peer.Peer, msg *wire.MsgMerkleBlock) { ok <- msg }, OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { ok <- msg }, OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, OnReject: func(p *peer.Peer, msg *wire.MsgReject) { ok <- msg }, OnSendHeaders: func(p *peer.Peer, msg *wire.MsgSendHeaders) { ok <- msg }, }, UserAgentName: "peer", UserAgentVersion: "1.0", ChainParams: &chaincfg.MainNetParams, Services: wire.SFNodeBloom, } inConn, outConn := pipe( &conn{raddr: "10.0.0.1:8333"}, &conn{raddr: "10.0.0.2:8333"}, ) inPeer := peer.NewInboundPeer(peerCfg) inPeer.Connect(inConn) peerCfg.Listeners = peer.MessageListeners{ OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, } outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err %v\n", err) return } outPeer.Connect(outConn) for i := 0; i < 2; i++ { select { case <-verack: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: verack timeout\n") return } } tests := []struct { listener string msg wire.Message }{ { "OnGetAddr", wire.NewMsgGetAddr(), }, { "OnAddr", wire.NewMsgAddr(), }, { "OnPing", wire.NewMsgPing(42), }, { "OnPong", wire.NewMsgPong(42), }, { "OnAlert", wire.NewMsgAlert([]byte("payload"), []byte("signature")), }, { "OnMemPool", wire.NewMsgMemPool(), }, { "OnTx", wire.NewMsgTx(), }, { "OnBlock", wire.NewMsgBlock(wire.NewBlockHeader(0, &chainhash.Hash{}, &chainhash.Hash{}, &chainhash.Hash{}, 1, [6]byte{}, 1, 1, 1, 1, 1, 1, 1, 1, 1, [32]byte{}, binary.LittleEndian.Uint32([]byte{0xb0, 0x1d, 0xfa, 0xce}))), }, { "OnInv", wire.NewMsgInv(), }, { "OnHeaders", wire.NewMsgHeaders(), }, { "OnNotFound", wire.NewMsgNotFound(), }, { "OnGetData", wire.NewMsgGetData(), }, { "OnGetBlocks", wire.NewMsgGetBlocks(&chainhash.Hash{}), }, { "OnGetHeaders", wire.NewMsgGetHeaders(), }, { "OnFilterAdd", wire.NewMsgFilterAdd([]byte{0x01}), }, { "OnFilterClear", wire.NewMsgFilterClear(), }, { "OnFilterLoad", wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone), }, { "OnMerkleBlock", wire.NewMsgMerkleBlock(wire.NewBlockHeader(0, &chainhash.Hash{}, &chainhash.Hash{}, &chainhash.Hash{}, 1, [6]byte{}, 1, 1, 1, 1, 1, 1, 1, 1, 1, [32]byte{}, binary.LittleEndian.Uint32([]byte{0xb0, 0x1d, 0xfa, 0xce}))), }, // only one version message is allowed // only one verack message is allowed { "OnReject", wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), }, { "OnSendHeaders", wire.NewMsgSendHeaders(), }, } t.Logf("Running %d tests", len(tests)) for _, test := range tests { // Queue the test message outPeer.QueueMessage(test.msg, nil) select { case <-ok: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: %s timeout", test.listener) return } } inPeer.Disconnect() outPeer.Disconnect() }
// This example demonstrates manually creating and signing a redeem transaction. func ExampleSignTxOutput() { // Ordinarily the private key would come from whatever storage mechanism // is being used, but for this example just hard code it. privKeyBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2" + "d4f8720ee63e502ee2869afab7de234b80c") if err != nil { fmt.Println(err) return } privKey, pubKey := chainec.Secp256k1.PrivKeyFromBytes(privKeyBytes) pubKeyHash := dcrutil.Hash160(pubKey.SerializeCompressed()) addr, err := dcrutil.NewAddressPubKeyHash(pubKeyHash, &chaincfg.MainNetParams, chainec.ECTypeSecp256k1) if err != nil { fmt.Println(err) return } // For this example, create a fake transaction that represents what // would ordinarily be the real transaction that is being spent. It // contains a single output that pays to address in the amount of 1 DCR. originTx := wire.NewMsgTx() prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0), dcrutil.TxTreeRegular) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}) originTx.AddTxIn(txIn) pkScript, err := txscript.PayToAddrScript(addr) if err != nil { fmt.Println(err) return } txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) originTxHash := originTx.TxSha() // Create the transaction to redeem the fake transaction. redeemTx := wire.NewMsgTx() // Add the input(s) the redeeming transaction will spend. There is no // signature script at this point since it hasn't been created or signed // yet, hence nil is provided for it. prevOut = wire.NewOutPoint(&originTxHash, 0, dcrutil.TxTreeRegular) txIn = wire.NewTxIn(prevOut, nil) redeemTx.AddTxIn(txIn) // Ordinarily this would contain that actual destination of the funds, // but for this example don't bother. txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) // Sign the redeeming transaction. lookupKey := func(a dcrutil.Address) (chainec.PrivateKey, bool, error) { // Ordinarily this function would involve looking up the private // key for the provided address, but since the only thing being // signed in this example uses the address associated with the // private key from above, simply return it with the compressed // flag set since the address is using the associated compressed // public key. // // NOTE: If you want to prove the code is actually signing the // transaction properly, uncomment the following line which // intentionally returns an invalid key to sign with, which in // turn will result in a failure during the script execution // when verifying the signature. // // privKey.D.SetInt64(12345) // return privKey, true, nil } // Notice that the script database parameter is nil here since it isn't // used. It must be specified when pay-to-script-hash transactions are // being signed. sigScript, err := txscript.SignTxOutput(&chaincfg.MainNetParams, redeemTx, 0, originTx.TxOut[0].PkScript, txscript.SigHashAll, txscript.KeyClosure(lookupKey), nil, nil, secp) if err != nil { fmt.Println(err) return } redeemTx.TxIn[0].SignatureScript = sigScript // Prove that the transaction has been validly signed by executing the // script pair. flags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures | txscript.ScriptDiscourageUpgradableNops vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0, flags, 0) if err != nil { fmt.Println(err) return } if err := vm.Execute(); err != nil { fmt.Println(err) return } fmt.Println("Transaction successfully signed") // Output: // Transaction successfully signed }
// TestTx tests the MsgTx API. func TestTx(t *testing.T) { pver := wire.ProtocolVersion // Block 100000 hash. hashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" hash, err := chainhash.NewHashFromStr(hashStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } // Ensure the command is expected value. wantCmd := "tx" msg := wire.NewMsgTx() if cmd := msg.Command(); cmd != wantCmd { t.Errorf("NewMsgAddr: wrong command - got %v want %v", cmd, wantCmd) } // Ensure max payload is expected value for latest protocol version. // Num addresses (varInt) + max allowed addresses. wantPayload := uint32(1000 * 1000) maxPayload := msg.MaxPayloadLength(pver) if maxPayload != wantPayload { t.Errorf("MaxPayloadLength: wrong max payload length for "+ "protocol version %d - got %v, want %v", pver, maxPayload, wantPayload) } // Ensure we get the same transaction output point data back out. // NOTE: This is a block hash and made up index, but we're only // testing package functionality. prevOutIndex := uint32(1) prevOut := wire.NewOutPoint(hash, prevOutIndex, dcrutil.TxTreeRegular) if !prevOut.Hash.IsEqual(hash) { t.Errorf("NewOutPoint: wrong hash - got %v, want %v", spew.Sprint(&prevOut.Hash), spew.Sprint(hash)) } if prevOut.Index != prevOutIndex { t.Errorf("NewOutPoint: wrong index - got %v, want %v", prevOut.Index, prevOutIndex) } prevOutStr := fmt.Sprintf("%s:%d", hash.String(), prevOutIndex) if s := prevOut.String(); s != prevOutStr { t.Errorf("OutPoint.String: unexpected result - got %v, "+ "want %v", s, prevOutStr) } // Ensure we get the same transaction input back out. sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62} txIn := wire.NewTxIn(prevOut, sigScript) if !reflect.DeepEqual(&txIn.PreviousOutPoint, prevOut) { t.Errorf("NewTxIn: wrong prev outpoint - got %v, want %v", spew.Sprint(&txIn.PreviousOutPoint), spew.Sprint(prevOut)) } if !bytes.Equal(txIn.SignatureScript, sigScript) { t.Errorf("NewTxIn: wrong signature script - got %v, want %v", spew.Sdump(txIn.SignatureScript), spew.Sdump(sigScript)) } // Ensure we get the same transaction output back out. txValue := int64(5000000000) pkScript := []byte{ 0x41, // OP_DATA_65 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, 0xa6, // 65-byte signature 0xac, // OP_CHECKSIG } txOut := wire.NewTxOut(txValue, pkScript) if txOut.Value != txValue { t.Errorf("NewTxOut: wrong pk script - got %v, want %v", txOut.Value, txValue) } if !bytes.Equal(txOut.PkScript, pkScript) { t.Errorf("NewTxOut: wrong pk script - got %v, want %v", spew.Sdump(txOut.PkScript), spew.Sdump(pkScript)) } // Ensure transaction inputs are added properly. msg.AddTxIn(txIn) if !reflect.DeepEqual(msg.TxIn[0], txIn) { t.Errorf("AddTxIn: wrong transaction input added - got %v, want %v", spew.Sprint(msg.TxIn[0]), spew.Sprint(txIn)) } // Ensure transaction outputs are added properly. msg.AddTxOut(txOut) if !reflect.DeepEqual(msg.TxOut[0], txOut) { t.Errorf("AddTxIn: wrong transaction output added - got %v, want %v", spew.Sprint(msg.TxOut[0]), spew.Sprint(txOut)) } // Ensure the copy produced an identical transaction message. newMsg := msg.Copy() if !reflect.DeepEqual(newMsg, msg) { t.Errorf("Copy: mismatched tx messages - got %v, want %v", spew.Sdump(newMsg), spew.Sdump(msg)) } return }
// TestTxWire tests the MsgTx wire encode and decode for various numbers // of transaction inputs and outputs and protocol versions. func TestTxWire(t *testing.T) { // Empty tx message. noTx := wire.NewMsgTx() noTx.Version = 1 noTxEncoded := []byte{ 0x01, 0x00, 0x00, 0x00, // Version 0x00, // Varint for number of input transactions 0x00, // Varint for number of output transactions 0x00, 0x00, 0x00, 0x00, // Lock time 0x00, 0x00, 0x00, 0x00, // Expiry 0x00, // Varint for number of input signatures } tests := []struct { in *wire.MsgTx // Message to encode out *wire.MsgTx // Expected decoded message buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ // Latest protocol version with no transactions. { noTx, noTx, noTxEncoded, wire.ProtocolVersion, }, // Latest protocol version with multiple transactions. { multiTx, multiTx, multiTxEncoded, wire.ProtocolVersion, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode the message to wire format. var buf bytes.Buffer err := test.in.BtcEncode(&buf, test.pver) if err != nil { t.Errorf("BtcEncode #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("BtcEncode #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } // Decode the message from wire format. var msg wire.MsgTx rbuf := bytes.NewReader(test.buf) err = msg.BtcDecode(rbuf, test.pver) if err != nil { t.Errorf("BtcDecode #%d error %v", i, err) continue } if !reflect.DeepEqual(&msg, test.out) { t.Errorf("BtcDecode #%d\n got: %s want: %s", i, spew.Sdump(&msg), spew.Sdump(test.out)) continue } } }
// 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 }
// TestMessage tests the Read/WriteMessage and Read/WriteMessageN API. func TestMessage(t *testing.T) { pver := wire.ProtocolVersion // Create the various types of messages to test. // MsgVersion. addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} you, err := wire.NewNetAddress(addrYou, wire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } you.Timestamp = time.Time{} // Version message has zero value timestamp. addrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} me, err := wire.NewNetAddress(addrMe, wire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } me.Timestamp = time.Time{} // Version message has zero value timestamp. msgVersion := wire.NewMsgVersion(me, you, 123123, 0) msgVerack := wire.NewMsgVerAck() msgGetAddr := wire.NewMsgGetAddr() msgAddr := wire.NewMsgAddr() msgGetBlocks := wire.NewMsgGetBlocks(&chainhash.Hash{}) msgBlock := &testBlock msgInv := wire.NewMsgInv() msgGetData := wire.NewMsgGetData() msgNotFound := wire.NewMsgNotFound() msgTx := wire.NewMsgTx() msgPing := wire.NewMsgPing(123123) msgPong := wire.NewMsgPong(123123) msgGetHeaders := wire.NewMsgGetHeaders() msgHeaders := wire.NewMsgHeaders() msgAlert := wire.NewMsgAlert([]byte("payload"), []byte("signature")) msgMemPool := wire.NewMsgMemPool() msgFilterAdd := wire.NewMsgFilterAdd([]byte{0x01}) msgFilterClear := wire.NewMsgFilterClear() msgFilterLoad := wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone) bh := wire.NewBlockHeader( int32(0), // Version &chainhash.Hash{}, // PrevHash &chainhash.Hash{}, // MerkleRoot &chainhash.Hash{}, // StakeRoot uint16(0x0000), // VoteBits [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // FinalState uint16(0x0000), // Voters uint8(0x00), // FreshStake uint8(0x00), // Revocations uint32(0), // Poolsize uint32(0x00000000), // Bits int64(0x0000000000000000), // Sbits uint32(0), // Height uint32(0), // Size uint32(0x00000000), // Nonce [36]byte{}, // ExtraData ) msgMerkleBlock := wire.NewMsgMerkleBlock(bh) msgReject := wire.NewMsgReject("block", wire.RejectDuplicate, "duplicate block") tests := []struct { in wire.Message // Value to encode out wire.Message // Expected decoded value pver uint32 // Protocol version for wire encoding dcrnet wire.CurrencyNet // Network to use for wire encoding bytes int // Expected num bytes read/written }{ {msgVersion, msgVersion, pver, wire.MainNet, 125}, // [0] {msgVerack, msgVerack, pver, wire.MainNet, 24}, // [1] {msgGetAddr, msgGetAddr, pver, wire.MainNet, 24}, // [2] {msgAddr, msgAddr, pver, wire.MainNet, 25}, // [3] {msgGetBlocks, msgGetBlocks, pver, wire.MainNet, 61}, // [4] {msgBlock, msgBlock, pver, wire.MainNet, 522}, // [5] {msgInv, msgInv, pver, wire.MainNet, 25}, // [6] {msgGetData, msgGetData, pver, wire.MainNet, 25}, // [7] {msgNotFound, msgNotFound, pver, wire.MainNet, 25}, // [8] {msgTx, msgTx, pver, wire.MainNet, 39}, // [9] {msgPing, msgPing, pver, wire.MainNet, 32}, // [10] {msgPong, msgPong, pver, wire.MainNet, 32}, // [11] {msgGetHeaders, msgGetHeaders, pver, wire.MainNet, 61}, // [12] {msgHeaders, msgHeaders, pver, wire.MainNet, 25}, // [13] {msgAlert, msgAlert, pver, wire.MainNet, 42}, // [14] {msgMemPool, msgMemPool, pver, wire.MainNet, 24}, // [15] {msgFilterAdd, msgFilterAdd, pver, wire.MainNet, 26}, // [16] {msgFilterClear, msgFilterClear, pver, wire.MainNet, 24}, // [17] {msgFilterLoad, msgFilterLoad, pver, wire.MainNet, 35}, // [18] {msgMerkleBlock, msgMerkleBlock, pver, wire.MainNet, 215}, // [19] {msgReject, msgReject, pver, wire.MainNet, 79}, // [20] } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode to wire format. var buf bytes.Buffer nw, err := wire.WriteMessageN(&buf, test.in, test.pver, test.dcrnet) if err != nil { t.Errorf("WriteMessage #%d error %v", i, err) continue } // Ensure the number of bytes written match the expected value. if nw != test.bytes { t.Errorf("WriteMessage #%d unexpected num bytes "+ "written - got %d, want %d", i, nw, test.bytes) } // Decode from wire format. rbuf := bytes.NewReader(buf.Bytes()) nr, msg, _, err := wire.ReadMessageN(rbuf, test.pver, test.dcrnet) if err != nil { t.Errorf("ReadMessage #%d error %v, msg %v", i, err, spew.Sdump(msg)) continue } if !reflect.DeepEqual(msg, test.out) { t.Errorf("ReadMessage #%d\n got: %v want: %v", i, spew.Sdump(msg), spew.Sdump(test.out)) continue } // Ensure the number of bytes read match the expected value. if nr != test.bytes { t.Errorf("ReadMessage #%d unexpected num bytes read - "+ "got %d, want %d", i, nr, test.bytes) } } // Do the same thing for Read/WriteMessage, but ignore the bytes since // they don't return them. t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode to wire format. var buf bytes.Buffer err := wire.WriteMessage(&buf, test.in, test.pver, test.dcrnet) if err != nil { t.Errorf("WriteMessage #%d error %v", i, err) continue } // Decode from wire format. rbuf := bytes.NewReader(buf.Bytes()) msg, _, err := wire.ReadMessage(rbuf, test.pver, test.dcrnet) if err != nil { t.Errorf("ReadMessage #%d error %v, msg %v", i, err, spew.Sdump(msg)) continue } if !reflect.DeepEqual(msg, test.out) { t.Errorf("ReadMessage #%d\n got: %v want: %v", i, spew.Sdump(msg), spew.Sdump(test.out)) continue } } }
// TestTxSerializeWitnessValueSigning tests MsgTx serialize and deserialize. func TestTxSerializeWitnessValueSigning(t *testing.T) { noTx := wire.NewMsgTx() noTx.Version = 262145 noTxEncoded := []byte{ 0x01, 0x00, 0x04, 0x00, // Version 0x00, // Varint for number of input signatures } tests := []struct { in *wire.MsgTx // Message to encode out *wire.MsgTx // Expected decoded message buf []byte // Serialized data pkScriptLocs []int // Expected output script locations }{ // No transactions. { noTx, noTx, noTxEncoded, nil, }, // Multiple transactions. { multiTxWitnessValueSigning, multiTxWitnessValueSigning, multiTxWitnessValueSigningEncoded, nil, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Serialize the transaction. var buf bytes.Buffer err := test.in.Serialize(&buf) if err != nil { t.Errorf("Serialize #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("Serialize #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } // Test SerializeSize. sz := test.in.SerializeSize() actualSz := len(buf.Bytes()) if sz != actualSz { t.Errorf("Wrong serialize size #%d\n got: %s want: %s", i, sz, actualSz) } // Deserialize the transaction. var tx wire.MsgTx rbuf := bytes.NewReader(test.buf) err = tx.Deserialize(rbuf) if err != nil { t.Errorf("Deserialize #%d error %v", i, err) continue } if !reflect.DeepEqual(&tx, test.out) { t.Errorf("Deserialize #%d\n got: %s want: %s", i, spew.Sdump(&tx), spew.Sdump(test.out)) continue } // Ensure the public key script locations are accurate. pkScriptLocs := test.in.PkScriptLocs() if !reflect.DeepEqual(pkScriptLocs, test.pkScriptLocs) { t.Errorf("PkScriptLocs #%d\n got: %s want: %s", i, spew.Sdump(pkScriptLocs), spew.Sdump(test.pkScriptLocs)) continue } for j, loc := range pkScriptLocs { wantPkScript := test.in.TxOut[j].PkScript gotPkScript := test.buf[loc : loc+len(wantPkScript)] if !bytes.Equal(gotPkScript, wantPkScript) { t.Errorf("PkScriptLocs #%d:%d\n unexpected "+ "script got: %s want: %s", i, j, spew.Sdump(gotPkScript), spew.Sdump(wantPkScript)) } } } }
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) } } }
// deserializeWithdrawal deserializes the given byte slice into a dbWithdrawalRow, // converts it into an withdrawalInfo and returns it. This function must run // with the address manager unlocked. func deserializeWithdrawal(p *Pool, serialized []byte) (*withdrawalInfo, error) { var row dbWithdrawalRow if err := gob.NewDecoder(bytes.NewReader(serialized)).Decode(&row); err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize withdrawal information", err) } wInfo := &withdrawalInfo{ lastSeriesID: row.LastSeriesID, dustThreshold: row.DustThreshold, } chainParams := p.Manager().ChainParams() wInfo.requests = make([]OutputRequest, len(row.Requests)) // A map of requests indexed by OutBailmentID; needed to populate // WithdrawalStatus.Outputs later on. requestsByOID := make(map[OutBailmentID]OutputRequest) for i, req := range row.Requests { addr, err := dcrutil.DecodeAddress(req.Addr, chainParams) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize addr for requested output", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, newError(ErrWithdrawalStorage, "invalid addr for requested output", err) } request := OutputRequest{ Address: addr, Amount: req.Amount, PkScript: pkScript, Server: req.Server, Transaction: req.Transaction, } wInfo.requests[i] = request requestsByOID[request.outBailmentID()] = request } startAddr := row.StartAddress wAddr, err := p.WithdrawalAddress(startAddr.SeriesID, startAddr.Branch, startAddr.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize startAddress", err) } wInfo.startAddress = *wAddr cAddr, err := p.ChangeAddress(row.ChangeStart.SeriesID, row.ChangeStart.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize changeStart", err) } wInfo.changeStart = *cAddr // TODO: Copy over row.Status.nextInputAddr. Not done because StartWithdrawal // does not update that yet. nextChangeAddr := row.Status.NextChangeAddr cAddr, err = p.ChangeAddress(nextChangeAddr.SeriesID, nextChangeAddr.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize nextChangeAddress for withdrawal", err) } wInfo.status = WithdrawalStatus{ nextChangeAddr: *cAddr, fees: row.Status.Fees, outputs: make(map[OutBailmentID]*WithdrawalOutput, len(row.Status.Outputs)), sigs: row.Status.Sigs, transactions: make(map[Ntxid]changeAwareTx, len(row.Status.Transactions)), } for oid, output := range row.Status.Outputs { outpoints := make([]OutBailmentOutpoint, len(output.Outpoints)) for i, outpoint := range output.Outpoints { outpoints[i] = OutBailmentOutpoint{ ntxid: outpoint.Ntxid, index: outpoint.Index, amount: outpoint.Amount, } } wInfo.status.outputs[oid] = &WithdrawalOutput{ request: requestsByOID[output.OutBailmentID], status: output.Status, outpoints: outpoints, } } for ntxid, tx := range row.Status.Transactions { msgtx := wire.NewMsgTx() if err := msgtx.Deserialize(bytes.NewBuffer(tx.SerializedMsgTx)); err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize transaction", err) } wInfo.status.transactions[ntxid] = changeAwareTx{ MsgTx: msgtx, changeIdx: tx.ChangeIdx, } } return wInfo, nil }