// 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 *btcutil.Block) (*blockchain.UtxoViewpoint, error) { view := blockchain.NewUtxoViewpoint() for txIdx, tx := range block.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 { originOut := &txIn.PreviousOutPoint originTx, err := dbFetchTx(dbTx, &originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(btcutil.NewTx(originTx), 0) } } return view, nil }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy // based on the passed block height to the provided address. When the address // is nil, the coinbase transaction will instead be redeemable by anyone. // // See the comment for NewBlockTemplate for more information about why the nil // address handling is useful. func createCoinbaseTx(params *chaincfg.Params, coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address if one was // specified. Otherwise create a script that allows the coinbase to be // redeemable by anyone. var pkScript []byte if addr != nil { var err error pkScript, err = txscript.PayToAddrScript(addr) if err != nil { return nil, err } } else { var err error scriptBuilder := txscript.NewScriptBuilder() pkScript, err = scriptBuilder.AddOp(txscript.OP_TRUE).Script() if err != nil { return nil, err } } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, params), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// newUtxoViewpoint returns a new utxo view populated with outputs of the // provided source transactions as if there were available at the respective // block height specified in the heights slice. The length of the source txns // and source tx heights must match or it will panic. func newUtxoViewpoint(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) *blockchain.UtxoViewpoint { if len(sourceTxns) != len(sourceTxHeights) { panic("each transaction must have its block height specified") } view := blockchain.NewUtxoViewpoint() for i, tx := range sourceTxns { view.AddTxOuts(btcutil.NewTx(tx), sourceTxHeights[i]) } return view }
// TxHandler takes in transaction messages that come in from either a request // after an inv message or after a merkle block message. func (s *SPVCon) TxHandler(m *wire.MsgTx) { s.TS.OKMutex.Lock() height, ok := s.TS.OKTxids[m.TxSha()] s.TS.OKMutex.Unlock() if !ok { log.Printf("Tx %s unknown, will not ingest\n", m.TxSha().String()) return } // check for double spends // allTxs, err := s.TS.GetAllTxs() // if err != nil { // log.Printf("Can't get txs from db: %s", err.Error()) // return // } // dubs, err := CheckDoubleSpends(m, allTxs) // if err != nil { // log.Printf("CheckDoubleSpends error: %s", err.Error()) // return // } // if len(dubs) > 0 { // for i, dub := range dubs { // fmt.Printf("dub %d known tx %s and new tx %s are exclusive!!!\n", // i, dub.String(), m.TxSha().String()) // } // } utilTx := btcutil.NewTx(m) if !s.HardMode || s.TS.localFilter.MatchTxAndUpdate(utilTx) { hits, err := s.TS.Ingest(m, height) if err != nil { log.Printf("Incoming Tx error: %s\n", err.Error()) return } if hits == 0 && !s.HardMode { log.Printf("tx %s had no hits, filter false positive.", m.TxSha().String()) s.fPositives <- 1 // add one false positive to chan return } log.Printf("tx %s ingested and matches %d utxo/adrs.", m.TxSha().String(), hits) } }
// TxToString prints out some info about a transaction. for testing / debugging func TxToString(tx *wire.MsgTx) string { str := fmt.Sprintf("size %d vsize %d wsize %d locktime %d txid %s\n", tx.SerializeSize(), blockchain.GetTxVirtualSize(btcutil.NewTx(tx)), tx.SerializeSize(), tx.LockTime, tx.TxSha().String()) for i, in := range tx.TxIn { str += fmt.Sprintf("Input %d spends %s\n", i, in.PreviousOutPoint.String()) str += fmt.Sprintf("\tSigScript: %x\n", in.SignatureScript) for j, wit := range in.Witness { str += fmt.Sprintf("\twitness %d: %x\n", j, wit) } } for i, out := range tx.TxOut { if out != nil { str += fmt.Sprintf("output %d script: %x amt: %d\n", i, out.PkScript, out.Value) } else { str += fmt.Sprintf("output %d nil (WARNING)\n", i) } } return str }
// EstFee gives a fee estimate based on a tx and a sat/Byte target. // The TX should have all outputs, including the change address already // populated (with potentially 0 amount. Also it should have all inputs // populated, but inputs don't need to have sigscripts or witnesses // (it'll guess the sizes of sigs/wits that arent' filled in). func EstFee(otx *wire.MsgTx, spB int64) int64 { mtsig := make([]byte, 72) mtpub := make([]byte, 33) tx := otx.Copy() // iterate through txins, replacing subscript sigscripts with noise // sigs or witnesses for _, txin := range tx.TxIn { // check wpkh if len(txin.SignatureScript) == 22 && txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x14 { txin.SignatureScript = nil txin.Witness = make([][]byte, 2) txin.Witness[0] = mtsig txin.Witness[1] = mtpub } else if len(txin.SignatureScript) == 34 && txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x20 { // p2wsh -- sig lenght is a total guess! txin.SignatureScript = nil txin.Witness = make([][]byte, 3) // 3 sigs? totally guessing here txin.Witness[0] = mtsig txin.Witness[1] = mtsig txin.Witness[2] = mtsig } else { // assume everything else is p2pkh. Even though it's not txin.Witness = nil txin.SignatureScript = make([]byte, 105) // len of p2pkh sigscript } } fmt.Printf(TxToString(tx)) size := int64(blockchain.GetTxVirtualSize(btcutil.NewTx(tx))) fmt.Printf("%d spB, est vsize %d, fee %d\n", spB, size, size*spB) return size * spB }
// createCoinbaseTx returns a coinbase transaction paying an appropriate // subsidy based on the passed block height to the provided address. func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address, net *chaincfg.Params) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address. pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// TestCheckSerializedHeight tests the checkSerializedHeight function with // various serialized heights and also does negative tests to ensure errors // and handled properly. func TestCheckSerializedHeight(t *testing.T) { // Create an empty coinbase template to be used in the tests below. coinbaseOutpoint := wire.NewOutPoint(&chainhash.Hash{}, math.MaxUint32) coinbaseTx := wire.NewMsgTx(1) coinbaseTx.AddTxIn(wire.NewTxIn(coinbaseOutpoint, nil, nil)) // Expected rule errors. missingHeightError := blockchain.RuleError{ ErrorCode: blockchain.ErrMissingCoinbaseHeight, } badHeightError := blockchain.RuleError{ ErrorCode: blockchain.ErrBadCoinbaseHeight, } tests := []struct { sigScript []byte // Serialized data wantHeight int32 // Expected height err error // Expected error type }{ // No serialized height length. {[]byte{}, 0, missingHeightError}, // Serialized height length with no height bytes. {[]byte{0x02}, 0, missingHeightError}, // Serialized height length with too few height bytes. {[]byte{0x02, 0x4a}, 0, missingHeightError}, // Serialized height that needs 2 bytes to encode. {[]byte{0x02, 0x4a, 0x52}, 21066, nil}, // Serialized height that needs 2 bytes to encode, but backwards // endianness. {[]byte{0x02, 0x4a, 0x52}, 19026, badHeightError}, // Serialized height that needs 3 bytes to encode. {[]byte{0x03, 0x40, 0x0d, 0x03}, 200000, nil}, // Serialized height that needs 3 bytes to encode, but backwards // endianness. {[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, badHeightError}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { msgTx := coinbaseTx.Copy() msgTx.TxIn[0].SignatureScript = test.sigScript tx := btcutil.NewTx(msgTx) err := blockchain.TstCheckSerializedHeight(tx, test.wantHeight) if reflect.TypeOf(err) != reflect.TypeOf(test.err) { t.Errorf("checkSerializedHeight #%d wrong error type "+ "got: %v <%T>, want: %T", i, err, err, test.err) continue } if rerr, ok := err.(blockchain.RuleError); ok { trerr := test.err.(blockchain.RuleError) if rerr.ErrorCode != trerr.ErrorCode { t.Errorf("checkSerializedHeight #%d wrong "+ "error code got: %v, want: %v", i, rerr.ErrorCode, trerr.ErrorCode) continue } } } }
// Ingest puts a tx into the DB atomically. This can result in a // gain, a loss, or no result. Gain or loss in satoshis is returned. func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { var hits uint32 var err error var nUtxoBytes [][]byte // tx has been OK'd by SPV; check tx sanity utilTx := btcutil.NewTx(tx) // convert for validation // checks basic stuff like there are inputs and ouputs err = blockchain.CheckTransactionSanity(utilTx) if err != nil { return hits, err } // note that you can't check signatures; this is SPV. // 0 conf SPV means pretty much nothing. Anyone can say anything. spentOPs := make([][]byte, len(tx.TxIn)) // before entering into db, serialize all inputs of the ingested tx for i, txin := range tx.TxIn { spentOPs[i], err = outPointToBytes(&txin.PreviousOutPoint) if err != nil { return hits, err } } // go through txouts, and then go through addresses to match // generate PKscripts for all addresses wPKscripts := make([][]byte, len(ts.Adrs)) aPKscripts := make([][]byte, len(ts.Adrs)) for i, _ := range ts.Adrs { // iterate through all our addresses // convert regular address to witness address. (split adrs later) wa, err := btcutil.NewAddressWitnessPubKeyHash( ts.Adrs[i].PkhAdr.ScriptAddress(), ts.Param) if err != nil { return hits, err } wPKscripts[i], err = txscript.PayToAddrScript(wa) if err != nil { return hits, err } aPKscripts[i], err = txscript.PayToAddrScript(ts.Adrs[i].PkhAdr) if err != nil { return hits, err } } cachedSha := tx.TxSha() // iterate through all outputs of this tx, see if we gain for i, out := range tx.TxOut { for j, ascr := range aPKscripts { // detect p2wpkh witBool := false if bytes.Equal(out.PkScript, wPKscripts[j]) { witBool = true } if bytes.Equal(out.PkScript, ascr) || witBool { // new utxo found var newu Utxo // create new utxo and copy into it newu.AtHeight = height newu.KeyIdx = ts.Adrs[j].KeyIdx newu.Value = out.Value newu.IsWit = witBool // copy witness version from pkscript var newop wire.OutPoint newop.Hash = cachedSha newop.Index = uint32(i) newu.Op = newop b, err := newu.ToBytes() if err != nil { return hits, err } nUtxoBytes = append(nUtxoBytes, b) hits++ break // txos can match only 1 script } } } err = ts.StateDB.Update(func(btx *bolt.Tx) error { // get all 4 buckets duf := btx.Bucket(BKTUtxos) // sta := btx.Bucket(BKTState) old := btx.Bucket(BKTStxos) txns := btx.Bucket(BKTTxns) if duf == nil || old == nil || txns == nil { return fmt.Errorf("error: db not initialized") } // iterate through duffel bag and look for matches // this makes us lose money, which is regrettable, but we need to know. for _, nOP := range spentOPs { v := duf.Get(nOP) if v != nil { hits++ // do all this just to figure out value we lost x := make([]byte, len(nOP)+len(v)) copy(x, nOP) copy(x[len(nOP):], v) lostTxo, err := UtxoFromBytes(x) if err != nil { return err } // after marking for deletion, save stxo to old bucket var st Stxo // generate spent txo st.Utxo = lostTxo // assign outpoint st.SpendHeight = height // spent at height st.SpendTxid = cachedSha // spent by txid stxb, err := st.ToBytes() // serialize if err != nil { return err } err = old.Put(nOP, stxb) // write nOP:v outpoint:stxo bytes if err != nil { return err } err = duf.Delete(nOP) if err != nil { return err } } } // done losing utxos, next gain utxos // next add all new utxos to db, this is quick as the work is above for _, ub := range nUtxoBytes { err = duf.Put(ub[:36], ub[36:]) if err != nil { return err } } // if hits is nonzero it's a relevant tx and we should store it var buf bytes.Buffer tx.Serialize(&buf) err = txns.Put(cachedSha.Bytes(), buf.Bytes()) if err != nil { return err } return nil }) return hits, err }
// IngestBlock is like IngestMerkleBlock but aralphic // different enough that it's better to have 2 separate functions func (s *SPVCon) IngestBlock(m *wire.MsgBlock) { var err error // var buf bytes.Buffer // m.SerializeWitness(&buf) // fmt.Printf("block hex %x\n", buf.Bytes()) // for _, tx := range m.Transactions { // fmt.Printf("wtxid: %s\n", tx.WTxSha()) // fmt.Printf(" txid: %s\n", tx.TxSha()) // fmt.Printf("%d %s", i, TxToString(tx)) // } ok := BlockOK(*m) // check block self-consistency if !ok { fmt.Printf("block %s not OK!!11\n", m.BlockSha().String()) return } var hah HashAndHeight select { // select here so we don't block on an unrequested mblock case hah = <-s.blockQueue: // pop height off mblock queue break default: log.Printf("Unrequested full block") return } newBlockSha := m.Header.BlockSha() if !hah.blockhash.IsEqual(&newBlockSha) { log.Printf("full block out of order error") return } fPositive := 0 // local filter false positives reFilter := 10 // after that many false positives, regenerate filter. // 10? Making it up. False positives have disk i/o cost, and regenning // the filter also has costs. With a large local filter, false positives // should be rare. // iterate through all txs in the block, looking for matches. // use a local bloom filter to ignore txs that don't affect us for _, tx := range m.Transactions { utilTx := btcutil.NewTx(tx) if s.TS.localFilter.MatchTxAndUpdate(utilTx) { hits, err := s.TS.Ingest(tx, hah.height) if err != nil { log.Printf("Incoming Tx error: %s\n", err.Error()) return } if hits > 0 { // log.Printf("block %d tx %d %s ingested and matches %d utxo/adrs.", // hah.height, i, tx.TxSha().String(), hits) } else { fPositive++ // matched filter but no hits } } } if fPositive > reFilter { fmt.Printf("%d filter false positives in this block\n", fPositive) err = s.TS.Refilter() if err != nil { log.Printf("Refilter error: %s\n", err.Error()) return } } // write to db that we've sync'd to the height indicated in the // merkle block. This isn't QUITE true since we haven't actually gotten // the txs yet but if there are problems with the txs we should backtrack. err = s.TS.SetDBSyncHeight(hah.height) if err != nil { log.Printf("full block sync error: %s\n", err.Error()) return } fmt.Printf("ingested full block %s height %d OK\n", m.Header.BlockSha().String(), hah.height) if hah.final { // check sync end // don't set waitstate; instead, ask for headers again! // this way the only thing that triggers waitstate is asking for headers, // getting 0, calling AskForMerkBlocks(), and seeing you don't need any. // that way you are pretty sure you're synced up. err = s.AskForHeaders() if err != nil { log.Printf("Merkle block error: %s\n", err.Error()) return } } return }
// 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} dummySigScript := bytes.Repeat([]byte{0x00}, 65) dummyTxIn := wire.TxIn{ PreviousOutPoint: dummyPrevOut, SignatureScript: dummySigScript, Sequence: wire.MaxTxInSequenceNum, } addrHash := [20]byte{0x01} addr, err := btcutil.NewAddressPubKeyHash(addrHash[:], &chaincfg.TestNet3Params) 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 PkScript: dummyPkScript, } tests := []struct { name string tx wire.MsgTx height int32 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: 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 one nulldata output", 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}, }}, 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, }, } pastMedianTime := time.Now() for _, test := range tests { // Ensure standardness is as expected. err := checkTransactionStandard(btcutil.NewTx(&test.tx), test.height, pastMedianTime, DefaultMinRelayTxFee, 1) 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 } } }
func testGenerateAndSubmitBlock(r *Harness, t *testing.T) { // Generate a few test spend transactions. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate new address: %v", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create script: %v", err) } output := wire.NewTxOut(btcutil.SatoshiPerBitcoin, pkScript) const numTxns = 5 txns := make([]*btcutil.Tx, 0, numTxns) for i := 0; i < numTxns; i++ { tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("unable to create tx: %v", err) } txns = append(txns, btcutil.NewTx(tx)) } // Now generate a block with the default block version, and a zero'd // out time. block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to generate block: %v", err) } // Ensure that all created transactions were included, and that the // block version was properly set to the default. numBlocksTxns := len(block.Transactions()) if numBlocksTxns != numTxns+1 { t.Fatalf("block did not include all transactions: "+ "expected %v, got %v", numTxns+1, numBlocksTxns) } blockVersion := block.MsgBlock().Header.Version if blockVersion != BlockVersion { t.Fatalf("block version is not default: expected %v, got %v", BlockVersion, blockVersion) } // Next generate a block with a "non-standard" block version along with // time stamp a minute after the previous block's timestamp. timestamp := block.MsgBlock().Header.Timestamp.Add(time.Minute) targetBlockVersion := int32(1337) block, err = r.GenerateAndSubmitBlock(nil, targetBlockVersion, timestamp) if err != nil { t.Fatalf("unable to generate block: %v", err) } // Finally ensure that the desired block version and timestamp were set // properly. header := block.MsgBlock().Header blockVersion = header.Version if blockVersion != targetBlockVersion { t.Fatalf("block version mismatch: expected %v, got %v", targetBlockVersion, blockVersion) } if !timestamp.Equal(header.Timestamp) { t.Fatalf("header time stamp mismatch: expected %v, got %v", timestamp, header.Timestamp) } }
// TestCalcSequenceLock tests the LockTimeToSequence function, and the // CalcSequenceLock method of a Chain instance. The tests exercise several // combinations of inputs to the CalcSequenceLock function in order to ensure // the returned SequenceLocks are correct for each test instance. func TestCalcSequenceLock(t *testing.T) { netParams := &chaincfg.SimNetParams // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("calcseqlock", netParams) if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) chain.TstSetCoinbaseMaturity(1) // Create a test mining address to use for the blocks we'll generate // shortly below. k := bytes.Repeat([]byte{1}, 32) _, miningPub := btcec.PrivKeyFromBytes(btcec.S256(), k) miningAddr, err := btcutil.NewAddressPubKey(miningPub.SerializeCompressed(), netParams) if err != nil { t.Fatalf("unable to generate mining addr: %v", err) } // We'll keep track of the previous block for back pointers in blocks // we generated, and also the generated blocks along with the MTP from // their PoV to aide with our relative time lock calculations. var prevBlock *btcutil.Block var blocksWithMTP []struct { block *btcutil.Block mtp time.Time } // We need to activate CSV in order to test the processing logic, so // manually craft the block version that's used to signal the soft-fork // activation. csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber blockVersion := int32(0x20000000 | (uint32(1) << csvBit)) // Generate enough blocks to activate CSV, collecting each of the // blocks into a slice for later use. numBlocksToActivate := (netParams.MinerConfirmationWindow * 3) for i := uint32(0); i < numBlocksToActivate; i++ { block, err := rpctest.CreateBlock(prevBlock, nil, blockVersion, time.Time{}, miningAddr, netParams) if err != nil { t.Fatalf("unable to generate block: %v", err) } mtp := chain.BestSnapshot().MedianTime _, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone) if err != nil { t.Fatalf("ProcessBlock fail on block %v: %v\n", i, err) } if isOrphan { t.Fatalf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) } blocksWithMTP = append(blocksWithMTP, struct { block *btcutil.Block mtp time.Time }{ block: block, mtp: mtp, }) prevBlock = block } // Create a utxo view with all the utxos within the blocks created // above. utxoView := blockchain.NewUtxoViewpoint() for blockHeight, blockWithMTP := range blocksWithMTP { for _, tx := range blockWithMTP.block.Transactions() { utxoView.AddTxOuts(tx, int32(blockHeight)) } } utxoView.SetBestHash(blocksWithMTP[len(blocksWithMTP)-1].block.Hash()) // The median time calculated from the PoV of the best block in our // test chain. For unconfirmed inputs, this value will be used since // the MTP will be calculated from the PoV of the yet-to-be-mined // block. nextMedianTime := int64(1401292712) // We'll refer to this utxo within each input in the transactions // created below. This utxo has an age of 4 blocks and was mined within // block 297 targetTx := blocksWithMTP[len(blocksWithMTP)-4].block.Transactions()[0] utxo := wire.OutPoint{ Hash: *targetTx.Hash(), Index: 0, } // Obtain the median time past from the PoV of the input created above. // The MTP for the input is the MTP from the PoV of the block *prior* // to the one that included it. medianTime := blocksWithMTP[len(blocksWithMTP)-5].mtp.Unix() // Add an additional transaction which will serve as our unconfirmed // output. var fakeScript []byte unConfTx := &wire.MsgTx{ TxOut: []*wire.TxOut{{ PkScript: fakeScript, Value: 5, }}, } unConfUtxo := wire.OutPoint{ Hash: unConfTx.TxHash(), Index: 0, } // Adding a utxo with a height of 0x7fffffff indicates that the output // is currently unmined. utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff) tests := []struct { tx *btcutil.Tx view *blockchain.UtxoViewpoint want *blockchain.SequenceLock mempool bool }{ // A transaction of version one should disable sequence locks // as the new sequence number semantics only apply to // transactions version 2 or higher. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input, that a max int sequence // number. This sequence number has the high bit set, so // sequence locks should be disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: wire.MaxTxInSequenceNum, }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. However, the specified lock time is // below the required floor for time based lock times since // they have time granularity of 512 seconds. As a result, the // seconds lock-time should be just before the median time of // the targeted block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime - 1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. The number of seconds should be 1023 // seconds after the median past time of the last block in the // chain. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + 1023, BlockHeight: -1, }, }, // A transaction with multiple inputs. The first input has a // sequence lock in blocks with a value of 4. The last input // has a sequence number with a value of 5, but has the disable // bit set. So the first lock should be selected as it's the // target lock as its the furthest in the future lock that // isn't disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 5) | wire.SequenceLockTimeDisabled, }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 4), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 299, }, }, // Transaction has a single input spending the genesis block // transaction. The input's sequence number is encodes a // relative lock-time in blocks (3 blocks). The sequence lock // should have a value of -1 for seconds, but a block height of // 298 meaning it can be included at height 299. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 298, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for seconds should // be the time further in the future. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 5120), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: -1, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for blocks should // be the height further in the future. The converted absolute // block height should be 302, meaning it can be included in // block 303. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 1), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 7), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 302, }, }, // A transaction with multiple inputs. Two inputs are time // based, and the other two are input maturity based. The lock // lying further into the future for both inputs should be // chosen. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 6656), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 9), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 304, }, }, // A transaction with a single unconfirmed input. As the input // is confirmed, the height of the input should be interpreted // as the height of the *next* block. The current block height // is 300, so the lock time should be calculated using height // 301 as a base. A 2 block relative lock means the transaction // can be included after block 302, so in 303. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(false, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 302, }, }, // A transaction with a single unconfirmed input. The input has // a time based lock, so the lock time should be based off the // MTP of the *next* block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: nextMedianTime + 1023, BlockHeight: -1, }, }, } t.Logf("Running %v SequenceLock tests", len(tests)) for i, test := range tests { seqLock, err := chain.CalcSequenceLock(test.tx, test.view, test.mempool) if err != nil { t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err) } if seqLock.Seconds != test.want.Seconds { t.Fatalf("test #%d got %v seconds want %v seconds", i, seqLock.Seconds, test.want.Seconds) } if seqLock.BlockHeight != test.want.BlockHeight { t.Fatalf("test #%d got height of %v want height of %v ", i, seqLock.BlockHeight, test.want.BlockHeight) } } }
func TestInsertsCreditsDebitsRollbacks(t *testing.T) { t.Parallel() // Create a double spend of the received blockchain transaction. dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx) // Switch txout amount to 1 BTC. 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 := btcutil.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), []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 := btcutil.NewTx(spendingTx) TstSpendingSerializedTx := serializeTx(TstSpendingTx) var _ = TstSpendingTx tests := []struct { name string f func(*Store) (*Store, error) bal, unc btcutil.Amount unspents map[wire.OutPoint]struct{} unmined map[wire.ShaHash]struct{} }{ { name: "new store", f: func(s *Store) (*Store, error) { return s, nil }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[wire.ShaHash]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: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback confirmed credit", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstDoubleSpendTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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[wire.ShaHash]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[wire.ShaHash]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: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback after spending tx", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height + 1) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback spending tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, }, unmined: map[wire.ShaHash]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: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, }, unmined: map[wire.ShaHash]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) 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) 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) } } }
// TestBIP0068AndBIP0112Activation tests for the proper adherence to the BIP // 112 and BIP 68 rule-set after the activation of the CSV-package soft-fork. // // Overview: // - Pre soft-fork: // - A transaction spending a CSV output validly should be rejected from the // mempool, but accepted in a valid generated block including the // transaction. // - Post soft-fork: // - See the cases exercised within the table driven tests towards the end // of this test. func TestBIP0068AndBIP0112Activation(t *testing.T) { t.Parallel() // We'd like the test proper evaluation and validation of the BIP 68 // (sequence locks) and BIP 112 rule-sets which add input-age based // relative lock times. btcdCfg := []string{"--rejectnonstd"} r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { t.Fatal("unable to create primary harness: ", err) } if err := r.SetUp(true, 1); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdStarted) harnessAddr, err := r.NewAddress() if err != nil { t.Fatalf("unable to obtain harness address: %v", err) } harnessScript, err := txscript.PayToAddrScript(harnessAddr) if err != nil { t.Fatalf("unable to generate pkScript: %v", err) } const ( outputAmt = btcutil.SatoshiPerBitcoin relativeBlockLock = 10 ) sweepOutput := &wire.TxOut{ Value: outputAmt - 5000, PkScript: harnessScript, } // As the soft-fork hasn't yet activated _any_ transaction version // which uses the CSV opcode should be accepted. Since at this point, // CSV doesn't actually exist, it's just a NOP. for txVersion := int32(0); txVersion < 3; txVersion++ { // Create a trivially spendable output with a CSV lock-time of // 10 relative blocks. redeemScript, testUTXO, tx, err := createCSVOutput(r, t, outputAmt, relativeBlockLock, false) if err != nil { t.Fatalf("unable to create CSV encumbered output: %v", err) } // As the transaction is p2sh it should be accepted into the // mempool and found within the next generated block. if _, err := r.Node.SendRawTransaction(tx, true); err != nil { t.Fatalf("unable to broadcast tx: %v", err) } blocks, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to generate blocks: %v", err) } txid := tx.TxHash() assertTxInBlock(r, t, blocks[0], &txid) // Generate a custom transaction which spends the CSV output. sequenceNum := blockchain.LockTimeToSequence(false, 10) spendingTx, err := spendCSVOutput(redeemScript, testUTXO, sequenceNum, sweepOutput, txVersion) if err != nil { t.Fatalf("unable to spend csv output: %v", err) } // This transaction should be rejected from the mempool since // CSV validation is already mempool policy pre-fork. _, err = r.Node.SendRawTransaction(spendingTx, true) if err == nil { t.Fatalf("transaction should have been rejected, but was " + "instead accepted") } // However, this transaction should be accepted in a custom // generated block as CSV validation for scripts within blocks // shouldn't yet be active. txns := []*btcutil.Tx{btcutil.NewTx(spendingTx)} block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to submit block: %v", err) } txid = spendingTx.TxHash() assertTxInBlock(r, t, block.Hash(), &txid) } // At this point, the block height should be 107: we started at height // 101, then generated 2 blocks in each loop iteration above. assertChainHeight(r, t, 107) // With the height at 107 we need 200 blocks to be mined after the // genesis target period, so we mine 192 blocks. This'll put us at // height 299. The getblockchaininfo call checks the state for the // block AFTER the current height. numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 8 if _, err := r.Node.Generate(numBlocks); err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 299) assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive) // Knowing the number of outputs needed for the tests below, create a // fresh output for use within each of the test-cases below. const relativeTimeLock = 512 const numTests = 8 type csvOutput struct { RedeemScript []byte Utxo *wire.OutPoint Timelock int32 } var spendableInputs [numTests]csvOutput // Create three outputs which have a block-based sequence locks, and // three outputs which use the above time based sequence lock. for i := 0; i < numTests; i++ { timeLock := relativeTimeLock isSeconds := true if i < 7 { timeLock = relativeBlockLock isSeconds = false } redeemScript, utxo, tx, err := createCSVOutput(r, t, outputAmt, int32(timeLock), isSeconds) if err != nil { t.Fatalf("unable to create CSV output: %v", err) } if _, err := r.Node.SendRawTransaction(tx, true); err != nil { t.Fatalf("unable to broadcast transaction: %v", err) } spendableInputs[i] = csvOutput{ RedeemScript: redeemScript, Utxo: utxo, Timelock: int32(timeLock), } } // Mine a single block including all the transactions generated above. if _, err := r.Node.Generate(1); err != nil { t.Fatalf("unable to generate block: %v", err) } // Now mine 10 additional blocks giving the inputs generated above a // age of 11. Space out each block 10 minutes after the previous block. prevBlockHash, err := r.Node.GetBestBlockHash() if err != nil { t.Fatalf("unable to get prior block hash: %v", err) } prevBlock, err := r.Node.GetBlock(prevBlockHash) if err != nil { t.Fatalf("unable to get block: %v", err) } for i := 0; i < relativeBlockLock; i++ { timeStamp := prevBlock.Header.Timestamp.Add(time.Minute * 10) b, err := r.GenerateAndSubmitBlock(nil, -1, timeStamp) if err != nil { t.Fatalf("unable to generate block: %v", err) } prevBlock = b.MsgBlock() } // A helper function to create fully signed transactions in-line during // the array initialization below. var inputIndex uint32 makeTxCase := func(sequenceNum uint32, txVersion int32) *wire.MsgTx { csvInput := spendableInputs[inputIndex] tx, err := spendCSVOutput(csvInput.RedeemScript, csvInput.Utxo, sequenceNum, sweepOutput, txVersion) if err != nil { t.Fatalf("unable to spend CSV output: %v", err) } inputIndex++ return tx } tests := [numTests]struct { tx *wire.MsgTx accept bool }{ // A valid transaction with a single input a sequence number // creating a 100 block relative time-lock. This transaction // should be rejected as its version number is 1, and only tx // of version > 2 will trigger the CSV behavior. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 100), 1), accept: false, }, // A transaction of version 2 spending a single input. The // input has a relative time-lock of 1 block, but the disable // bit it set. The transaction should be rejected as a result. { tx: makeTxCase( blockchain.LockTimeToSequence(false, 1)|wire.SequenceLockTimeDisabled, 2, ), accept: false, }, // A v2 transaction with a single input having a 9 block // relative time lock. The referenced input is 11 blocks old, // but the CSV output requires a 10 block relative lock-time. // Therefore, the transaction should be rejected. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 9), 2), accept: false, }, // A v2 transaction with a single input having a 10 block // relative time lock. The referenced input is 11 blocks old so // the transaction should be accepted. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 10), 2), accept: true, }, // A v2 transaction with a single input having a 11 block // relative time lock. The input referenced has an input age of // 11 and the CSV op-code requires 10 blocks to have passed, so // this transaction should be accepted. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 11), 2), accept: true, }, // A v2 transaction whose input has a 1000 blck relative time // lock. This should be rejected as the input's age is only 11 // blocks. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 1000), 2), accept: false, }, // A v2 transaction with a single input having a 512,000 second // relative time-lock. This transaction should be rejected as 6 // days worth of blocks haven't yet been mined. The referenced // input doesn't have sufficient age. { tx: makeTxCase(blockchain.LockTimeToSequence(true, 512000), 2), accept: false, }, // A v2 transaction whose single input has a 512 second // relative time-lock. This transaction should be accepted as // finalized. { tx: makeTxCase(blockchain.LockTimeToSequence(true, 512), 2), accept: true, }, } for i, test := range tests { txid, err := r.Node.SendRawTransaction(test.tx, true) switch { // Test case passes, nothing further to report. case test.accept && err == nil: // Transaction should have been accepted but we have a non-nil // error. case test.accept && err != nil: t.Fatalf("test #%d, transaction should be accepted, "+ "but was rejected: %v", i, err) // Transaction should have been rejected, but it was accepted. case !test.accept && err == nil: t.Fatalf("test #%d, transaction should be rejected, "+ "but was accepted", i) // Transaction was rejected as wanted, nothing more to do. case !test.accept && err != nil: } // If the transaction should be rejected, manually mine a block // with the non-final transaction. It should be rejected. if !test.accept { txns := []*btcutil.Tx{btcutil.NewTx(test.tx)} _, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err == nil { t.Fatalf("test #%d, invalid block accepted", i) } continue } // Generate a block, the transaction should be included within // the newly mined block. blockHashes, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to mine block: %v", err) } assertTxInBlock(r, t, blockHashes[0], txid) } }
// TestBIP0113Activation tests for proper adherance of the BIP 113 rule // constraint which requires all transaction finality tests to use the MTP of // the last 11 blocks, rather than the timestamp of the block which includes // them. // // Overview: // - Pre soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool. // - Transactions within non-final MTP based lock-times should be accepted // in valid blocks. // // - Post soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool and when found within otherwise valid blocks. // - Transactions with final lock-times from the PoV of MTP should be // accepted to the mempool and mined in future block. func TestBIP0113Activation(t *testing.T) { t.Parallel() btcdCfg := []string{"--rejectnonstd"} r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { t.Fatal("unable to create primary harness: ", err) } if err := r.SetUp(true, 1); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() // Create a fresh output for usage within the test below. const outputValue = btcutil.SatoshiPerBitcoin outputKey, testOutput, testPkScript, err := makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Fetch a fresh address from the harness, we'll use this address to // send funds back into the Harness. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate address: %v", err) } addrScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to generate addr script: %v", err) } // Now create a transaction with a lock time which is "final" according // to the latest block, but not according to the current median time // past. tx := wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) // We set the lock-time of the transaction to just one minute after the // current MTP of the chain. chainInfo, err := r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } tx.LockTime = uint32(chainInfo.MedianTime) + 1 sigScript, err := txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // This transaction should be rejected from the mempool as using MTP // for transactions finality is now a policy rule. Additionally, the // exact error should be the rejection of a non-final transaction. _, err = r.Node.SendRawTransaction(tx, true) if err == nil { t.Fatalf("transaction accepted, but should be non-final") } else if !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected due to being "+ "non-final, instead: %v", err) } // However, since the block validation consensus rules haven't yet // activated, a block including the transaction should be accepted. txns := []*btcutil.Tx{btcutil.NewTx(tx)} block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to submit block: %v", err) } txid := tx.TxHash() assertTxInBlock(r, t, block.Hash(), &txid) // At this point, the block height should be 103: we mined 101 blocks // to create a single mature output, then an additional block to create // a new output, and then mined a single block above to include our // transation. assertChainHeight(r, t, 103) // Next, mine enough blocks to ensure that the soft-fork becomes // activated. Assert that the block version of the second-to-last block // in the final range is active. // Next, mine ensure blocks to ensure that the soft-fork becomes // active. We're at height 103 and we need 200 blocks to be mined after // the genesis target period, so we mine 196 blocks. This'll put us at // height 299. The getblockchaininfo call checks the state for the // block AFTER the current height. numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4 if _, err := r.Node.Generate(numBlocks); err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 299) assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive) // The timeLockDeltas slice represents a series of deviations from the // current MTP which will be used to test border conditions w.r.t // transaction finality. -1 indicates 1 second prior to the MTP, 0 // indicates the current MTP, and 1 indicates 1 second after the // current MTP. // // This time, all transactions which are final according to the MTP // *should* be accepted to both the mempool and within a valid block. // While transactions with lock-times *after* the current MTP should be // rejected. timeLockDeltas := []int64{-1, 0, 1} for _, timeLockDelta := range timeLockDeltas { chainInfo, err = r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } medianTimePast := chainInfo.MedianTime // Create another test output to be spent shortly below. outputKey, testOutput, testPkScript, err = makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Create a new transaction with a lock-time past the current known // MTP. tx = wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) tx.LockTime = uint32(medianTimePast + timeLockDelta) sigScript, err = txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // If the time-lock delta is greater than -1, then the // transaction should be rejected from the mempool and when // included within a block. A time-lock delta of -1 should be // accepted as it has a lock-time of one // second _before_ the current MTP. _, err = r.Node.SendRawTransaction(tx, true) if err == nil && timeLockDelta >= 0 { t.Fatal("transaction was accepted into the mempool " + "but should be rejected!") } else if err != nil && !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected from mempool "+ "due to being non-final, instead: %v", err) } txns = []*btcutil.Tx{btcutil.NewTx(tx)} _, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err == nil && timeLockDelta >= 0 { t.Fatal("block should be rejected due to non-final " + "txn, but was accepted") } else if err != nil && !strings.Contains(err.Error(), "unfinalized") { t.Fatalf("block should be rejected due to non-final "+ "tx, instead: %v", err) } } }