diff --git a/db/migrations/00041_create_btc_header_cids_table.sql b/db/migrations/00041_create_btc_header_cids_table.sql index 1021ebcd..62ffb14b 100644 --- a/db/migrations/00041_create_btc_header_cids_table.sql +++ b/db/migrations/00041_create_btc_header_cids_table.sql @@ -5,9 +5,8 @@ CREATE TABLE btc.header_cids ( block_hash VARCHAR(66) NOT NULL, parent_hash VARCHAR(66) NOT NULL, cid TEXT NOT NULL, - version INTEGER NOT NULL, - timestamp INTEGER NOT NULL, - bits INTEGER NOT NULL, + timestamp NUMERIC NOT NULL, + bits BIGINT NOT NULL, UNIQUE (block_number, block_hash) ); diff --git a/db/migrations/00043_create_btc_tx_inputs_table.sql b/db/migrations/00043_create_btc_tx_inputs_table.sql index 61619af8..eb3746fa 100644 --- a/db/migrations/00043_create_btc_tx_inputs_table.sql +++ b/db/migrations/00043_create_btc_tx_inputs_table.sql @@ -1,12 +1,12 @@ -- +goose Up CREATE TABLE btc.tx_inputs ( - id SERIAL PRIMARY KEY, - tx_id INTEGER NOT NULL REFERENCES btc.transaction_cids (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - index INTEGER NOT NULL, - witness BYTEA[], - sig_script BYTEA NOT NULL, - outpoint_tx_id INTEGER NOT NULL REFERENCES btc.transaction_cids (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, - outpoint_index INTEGER NOT NULL, + id SERIAL PRIMARY KEY, + tx_id INTEGER NOT NULL REFERENCES btc.transaction_cids (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + index INTEGER NOT NULL, + witness BYTEA[], + sig_script BYTEA NOT NULL, + outpoint_tx_hash VARCHAR(66) REFERENCES btc.transaction_cids (tx_hash) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + outpoint_index BIGINT NOT NULL, UNIQUE (tx_id, index) ); diff --git a/db/migrations/00044_create_btc_tx_outputs_table.sql b/db/migrations/00044_create_btc_tx_outputs_table.sql index ae95b72d..d7d340ba 100644 --- a/db/migrations/00044_create_btc_tx_outputs_table.sql +++ b/db/migrations/00044_create_btc_tx_outputs_table.sql @@ -3,7 +3,7 @@ CREATE TABLE btc.tx_outputs ( id SERIAL PRIMARY KEY, tx_id INTEGER NOT NULL REFERENCES btc.transaction_cids (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, index INTEGER NOT NULL, - value INTEGER NOT NULL, + value BIGINT NOT NULL, pk_script BYTEA NOT NULL, script_class INTEGER NOT NULL, addresses VARCHAR(66)[], diff --git a/db/schema.sql b/db/schema.sql index 932d51a5..206dc16e 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -42,9 +42,8 @@ CREATE TABLE btc.header_cids ( block_hash character varying(66) NOT NULL, parent_hash character varying(66) NOT NULL, cid text NOT NULL, - version integer NOT NULL, - "timestamp" integer NOT NULL, - bits integer NOT NULL + "timestamp" numeric NOT NULL, + bits bigint NOT NULL ); @@ -113,8 +112,8 @@ CREATE TABLE btc.tx_inputs ( index integer NOT NULL, witness bytea[], sig_script bytea NOT NULL, - outpoint_tx_id integer NOT NULL, - outpoint_index integer NOT NULL + outpoint_tx_hash character varying(66), + outpoint_index bigint NOT NULL ); @@ -146,7 +145,7 @@ CREATE TABLE btc.tx_outputs ( id integer NOT NULL, tx_id integer NOT NULL, index integer NOT NULL, - value integer NOT NULL, + value bigint NOT NULL, pk_script bytea NOT NULL, script_class integer NOT NULL, addresses character varying(66)[], @@ -1747,11 +1746,11 @@ ALTER TABLE ONLY btc.transaction_cids -- --- Name: tx_inputs tx_inputs_outpoint_tx_id_fkey; Type: FK CONSTRAINT; Schema: btc; Owner: - +-- Name: tx_inputs tx_inputs_outpoint_tx_hash_fkey; Type: FK CONSTRAINT; Schema: btc; Owner: - -- ALTER TABLE ONLY btc.tx_inputs - ADD CONSTRAINT tx_inputs_outpoint_tx_id_fkey FOREIGN KEY (outpoint_tx_id) REFERENCES btc.transaction_cids(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + ADD CONSTRAINT tx_inputs_outpoint_tx_hash_fkey FOREIGN KEY (outpoint_tx_hash) REFERENCES btc.transaction_cids(tx_hash) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; -- diff --git a/pkg/super_node/btc/btc_suite_test.go b/pkg/super_node/btc/btc_suite_test.go index ca21ebee..ba716427 100644 --- a/pkg/super_node/btc/btc_suite_test.go +++ b/pkg/super_node/btc/btc_suite_test.go @@ -25,7 +25,7 @@ import ( "github.com/sirupsen/logrus" ) -func TestETHSuperNode(t *testing.T) { +func TestBTCSuperNode(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Super Node BTC Suite Test") } diff --git a/pkg/super_node/btc/converter.go b/pkg/super_node/btc/converter.go index 7a622149..51285204 100644 --- a/pkg/super_node/btc/converter.go +++ b/pkg/super_node/btc/converter.go @@ -32,7 +32,9 @@ type PayloadConverter struct { // NewPayloadConverter creates a pointer to a new PayloadConverter which satisfies the PayloadConverter interface func NewPayloadConverter(chainConfig *chaincfg.Params) *PayloadConverter { - return &PayloadConverter{} + return &PayloadConverter{ + chainConfig: chainConfig, + } } // Convert method is used to convert a bitcoin BlockPayload to an IPLDPayload @@ -43,11 +45,10 @@ func (pc *PayloadConverter) Convert(payload shared.RawChainData) (shared.Streame return nil, fmt.Errorf("btc converter: expected payload type %T got %T", BlockPayload{}, payload) } txMeta := make([]TxModelWithInsAndOuts, len(btcBlockPayload.Txs)) - for _, tx := range btcBlockPayload.Txs { - index := tx.Index() + for i, tx := range btcBlockPayload.Txs { txModel := TxModelWithInsAndOuts{ TxHash: tx.Hash().String(), - Index: int64(index), + Index: int64(i), SegWit: tx.HasWitness(), TxOutputs: make([]TxOutput, len(tx.MsgTx().TxOut)), TxInputs: make([]TxInput, len(tx.MsgTx().TxIn)), @@ -79,11 +80,11 @@ func (pc *PayloadConverter) Convert(payload shared.RawChainData) (shared.Streame Value: out.Value, PkScript: out.PkScript, RequiredSigs: int64(numberOfSigs), - ScriptClass: (uint8)(scriptClass), + ScriptClass: uint8(scriptClass), Addresses: stringAddrs, } } - txMeta[index] = txModel + txMeta[i] = txModel } return IPLDPayload{ BlockPayload: btcBlockPayload, diff --git a/pkg/super_node/btc/converter_test.go b/pkg/super_node/btc/converter_test.go new file mode 100644 index 00000000..c5ad628d --- /dev/null +++ b/pkg/super_node/btc/converter_test.go @@ -0,0 +1,43 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc_test + +import ( + "github.com/btcsuite/btcd/chaincfg" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks" +) + +var _ = Describe("Converter", func() { + Describe("Convert", func() { + It("Converts mock BlockPayloads into the expected IPLDPayloads", func() { + converter := btc.NewPayloadConverter(&chaincfg.MainNetParams) + payload, err := converter.Convert(mocks.MockBlockPayload) + Expect(err).ToNot(HaveOccurred()) + convertedPayload, ok := payload.(btc.IPLDPayload) + Expect(ok).To(BeTrue()) + Expect(convertedPayload).To(Equal(mocks.MockIPLDPayload)) + Expect(convertedPayload.Height).To(Equal(mocks.MockBlockHeight)) + Expect(convertedPayload.Header).To(Equal(&mocks.MockBlock.Header)) + Expect(convertedPayload.Txs).To(Equal(mocks.MockTransactions)) + Expect(convertedPayload.TxMetaData).To(Equal(mocks.MockTxsMetaData)) + }) + }) +}) diff --git a/pkg/super_node/btc/filterer.go b/pkg/super_node/btc/filterer.go index 8897734c..b9b6a78d 100644 --- a/pkg/super_node/btc/filterer.go +++ b/pkg/super_node/btc/filterer.go @@ -21,12 +21,10 @@ import ( "fmt" "math/big" - "github.com/btcsuite/btcutil" - "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" ) -// ResponseFilterer satisfies the ResponseFilterer interface for ethereum +// ResponseFilterer satisfies the ResponseFilterer interface for bitcoin type ResponseFilterer struct{} // NewResponseFilterer creates a new Filterer satisfying the ResponseFilterer interface @@ -34,15 +32,15 @@ func NewResponseFilterer() *ResponseFilterer { return &ResponseFilterer{} } -// Filter is used to filter through eth data to extract and package requested data into a Payload +// Filter is used to filter through btc data to extract and package requested data into a Payload func (s *ResponseFilterer) Filter(filter shared.SubscriptionSettings, payload shared.StreamedIPLDs) (shared.ServerResponse, error) { btcFilters, ok := filter.(*SubscriptionSettings) if !ok { - return StreamResponse{}, fmt.Errorf("eth filterer expected filter type %T got %T", &SubscriptionSettings{}, filter) + return StreamResponse{}, fmt.Errorf("btc filterer expected filter type %T got %T", &SubscriptionSettings{}, filter) } btcPayload, ok := payload.(IPLDPayload) if !ok { - return StreamResponse{}, fmt.Errorf("eth filterer expected payload type %T got %T", IPLDPayload{}, payload) + return StreamResponse{}, fmt.Errorf("btc filterer expected payload type %T got %T", IPLDPayload{}, payload) } height := int64(btcPayload.Height) if checkRange(btcFilters.Start.Int64(), btcFilters.End.Int64(), height) { @@ -79,10 +77,10 @@ func checkRange(start, end, actual int64) bool { func (s *ResponseFilterer) filterTransactions(trxFilter TxFilter, response *StreamResponse, payload IPLDPayload) error { if !trxFilter.Off { - for _, trx := range payload.Txs { - if checkTransaction(trx, trxFilter) { + for i, txMeta := range payload.TxMetaData { + if checkTransaction(txMeta, trxFilter) { trxBuffer := new(bytes.Buffer) - if err := trx.MsgTx().Serialize(trxBuffer); err != nil { + if err := payload.Txs[i].MsgTx().Serialize(trxBuffer); err != nil { return err } response.SerializedTxs = append(response.SerializedTxs, trxBuffer.Bytes()) @@ -93,6 +91,48 @@ func (s *ResponseFilterer) filterTransactions(trxFilter TxFilter, response *Stre } // checkTransaction returns true if the provided transaction has a hit on the filter -func checkTransaction(trx *btcutil.Tx, txFilter TxFilter) bool { - panic("implement me") +func checkTransaction(txMeta TxModelWithInsAndOuts, txFilter TxFilter) bool { + passesSegwitFilter := false + if !txFilter.Segwit || (txFilter.Segwit && txMeta.SegWit) { + passesSegwitFilter = true + } + passesMultiSigFilter := !txFilter.MultiSig + if txFilter.MultiSig { + for _, out := range txMeta.TxOutputs { + if out.RequiredSigs > 1 { + passesMultiSigFilter = true + } + } + } + passesWitnessFilter := len(txFilter.WitnessHashes) == 0 + for _, wantedWitnessHash := range txFilter.WitnessHashes { + if wantedWitnessHash == txMeta.WitnessHash { + passesWitnessFilter = true + } + } + passesAddressFilter := len(txFilter.Addresses) == 0 + for _, wantedAddress := range txFilter.Addresses { + for _, out := range txMeta.TxOutputs { + for _, actualAddress := range out.Addresses { + if wantedAddress == actualAddress { + passesAddressFilter = true + } + } + } + } + passesIndexFilter := len(txFilter.Indexes) == 0 + for _, wantedIndex := range txFilter.Indexes { + if wantedIndex == txMeta.Index { + passesIndexFilter = true + } + } + passesPkScriptClassFilter := len(txFilter.PkScriptClasses) == 0 + for _, wantedPkScriptClass := range txFilter.PkScriptClasses { + for _, out := range txMeta.TxOutputs { + if out.ScriptClass == wantedPkScriptClass { + passesPkScriptClassFilter = true + } + } + } + return passesSegwitFilter && passesMultiSigFilter && passesWitnessFilter && passesAddressFilter && passesIndexFilter && passesPkScriptClassFilter } diff --git a/pkg/super_node/btc/filterer_test.go b/pkg/super_node/btc/filterer_test.go new file mode 100644 index 00000000..8dd3c1ae --- /dev/null +++ b/pkg/super_node/btc/filterer_test.go @@ -0,0 +1,17 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc diff --git a/pkg/super_node/btc/indexer.go b/pkg/super_node/btc/indexer.go index 1f7554f9..6a8bf3f9 100644 --- a/pkg/super_node/btc/indexer.go +++ b/pkg/super_node/btc/indexer.go @@ -19,6 +19,8 @@ package btc import ( "fmt" + "github.com/sirupsen/logrus" + "github.com/jmoiron/sqlx" "github.com/lib/pq" @@ -47,9 +49,12 @@ func (in *CIDIndexer) Index(cids shared.CIDsForIndexing) error { } headerID, err := in.indexHeaderCID(tx, cidWrapper.HeaderCID) if err != nil { + println("err") + logrus.Error("btc indexer error when indexing header") return err } if err := in.indexTransactionCIDs(tx, cidWrapper.TransactionCIDs, headerID); err != nil { + logrus.Error("btc indexer error when indexing transactions") return err } return tx.Commit() @@ -57,11 +62,11 @@ func (in *CIDIndexer) Index(cids shared.CIDsForIndexing) error { func (in *CIDIndexer) indexHeaderCID(tx *sqlx.Tx, header HeaderModel) (int64, error) { var headerID int64 - err := tx.QueryRowx(`INSERT INTO btc.header_cids (block_number, block_hash, parent_hash, cid, version, timestamp, bits) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (block_number, block_hash) DO UPDATE SET (parent_hash, cid, version, timestamp, bits) = ($3, $4, $5, $6, $7) + err := tx.QueryRowx(`INSERT INTO btc.header_cids (block_number, block_hash, parent_hash, cid, timestamp, bits) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (block_number, block_hash) DO UPDATE SET (parent_hash, cid, timestamp, bits) = ($3, $4, $5, $6) RETURNING id`, - header.BlockNumber, header.BlockHash, header.ParentHash, header.CID, header.Version, header.Timestamp, header.Bits).Scan(&headerID) + header.BlockNumber, header.BlockHash, header.ParentHash, header.CID, header.Timestamp, header.Bits).Scan(&headerID) return headerID, err } @@ -69,15 +74,21 @@ func (in *CIDIndexer) indexTransactionCIDs(tx *sqlx.Tx, transactions []TxModelWi for _, transaction := range transactions { txID, err := in.indexTransactionCID(tx, transaction, headerID) if err != nil { + println(0) + logrus.Error("btc indexer error when indexing header") return err } for _, input := range transaction.TxInputs { if err := in.indexTxInput(tx, input, txID); err != nil { + println(1) + logrus.Error("btc indexer error when indexing tx inputs") return err } } for _, output := range transaction.TxOutputs { if err := in.indexTxOutput(tx, output, txID); err != nil { + println(2) + logrus.Error("btc indexer error when indexing tx outputs") return err } } @@ -96,25 +107,25 @@ func (in *CIDIndexer) indexTransactionCID(tx *sqlx.Tx, transaction TxModelWithIn } func (in *CIDIndexer) indexTxInput(tx *sqlx.Tx, txInput TxInput, txID int64) error { - var referencedOutPutTxID int64 - if err := tx.Get(&referencedOutPutTxID, `SELECT id FROM btc.transaction_cids - WHERE tx_hash = $1`, txInput.PreviousOutPointHash); err != nil { + // resolve zero-value hash to null value (coinbase tx input with no referenced outputs) + if txInput.PreviousOutPointHash == "0000000000000000000000000000000000000000000000000000000000000000" { + _, err := tx.Exec(`INSERT INTO btc.tx_inputs (tx_id, index, witness, sig_script, outpoint_tx_hash, outpoint_index) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (tx_id, index) DO UPDATE SET (witness, sig_script, outpoint_tx_hash, outpoint_index) = ($3, $4, $5, $6)`, + txID, txInput.Index, pq.Array(txInput.TxWitness), txInput.SignatureScript, nil, txInput.PreviousOutPointIndex) return err } - if referencedOutPutTxID == 0 { - return fmt.Errorf("btc indexer could not find the tx hash %s referenced in tx input of tx id %d", txInput.PreviousOutPointHash, txID) - } - _, err := tx.Exec(`INSERT INTO btc.tx_inputs (tx_id, index, witness, sig_script, outpoint_tx_id, outpoint_index) + _, err := tx.Exec(`INSERT INTO btc.tx_inputs (tx_id, index, witness, sig_script, outpoint_tx_hash, outpoint_index) VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT (tx_id, index) DO UPDATE SET (witness, sig_script, outpoint_tx_id, outpoint_index) = ($3, $4, $5, $6)`, - txID, txInput.Index, pq.Array(txInput.TxWitness), txInput.SignatureScript, referencedOutPutTxID, txInput.PreviousOutPointIndex) + ON CONFLICT (tx_id, index) DO UPDATE SET (witness, sig_script, outpoint_tx_hash, outpoint_index) = ($3, $4, $5, $6)`, + txID, txInput.Index, pq.Array(txInput.TxWitness), txInput.SignatureScript, txInput.PreviousOutPointHash, txInput.PreviousOutPointIndex) return err } func (in *CIDIndexer) indexTxOutput(tx *sqlx.Tx, txOuput TxOutput, txID int64) error { _, err := tx.Exec(`INSERT INTO btc.tx_outputs (tx_id, index, value, pk_script, script_class, addresses, required_sigs) VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (ix_id, index) DO UPDATE SET (value, pk_script, script_class, addresses, required_sigs) = ($3, $4, $5, $6, $7)`, + ON CONFLICT (tx_id, index) DO UPDATE SET (value, pk_script, script_class, addresses, required_sigs) = ($3, $4, $5, $6, $7)`, txID, txOuput.Index, txOuput.Value, txOuput.PkScript, txOuput.ScriptClass, txOuput.Addresses, txOuput.RequiredSigs) return err } diff --git a/pkg/super_node/btc/indexer_test.go b/pkg/super_node/btc/indexer_test.go new file mode 100644 index 00000000..d5a093b1 --- /dev/null +++ b/pkg/super_node/btc/indexer_test.go @@ -0,0 +1,89 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks" + "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" +) + +var _ = Describe("Indexer", func() { + var ( + db *postgres.DB + err error + repo *btc.CIDIndexer + ) + BeforeEach(func() { + db, err = shared.SetupDB() + Expect(err).ToNot(HaveOccurred()) + repo = btc.NewCIDIndexer(db) + }) + AfterEach(func() { + btc.TearDownDB(db) + }) + + Describe("Index", func() { + It("Indexes CIDs and related metadata into vulcanizedb", func() { + err = repo.Index(&mocks.DummyCIDPayloadForFKReference) + Expect(err).ToNot(HaveOccurred()) + err = repo.Index(&mocks.MockCIDPayload) + Expect(err).ToNot(HaveOccurred()) + pgStr := `SELECT * FROM btc.header_cids + WHERE block_number = $1` + // check header was properly indexed + header := new(btc.HeaderModel) + err = db.Get(header, pgStr, mocks.MockHeaderMetaData.BlockNumber) + Expect(err).ToNot(HaveOccurred()) + Expect(header.CID).To(Equal(mocks.MockHeaderMetaData.CID)) + Expect(header.BlockNumber).To(Equal(mocks.MockHeaderMetaData.BlockNumber)) + Expect(header.Bits).To(Equal(mocks.MockHeaderMetaData.Bits)) + Expect(header.Timestamp).To(Equal(mocks.MockHeaderMetaData.Timestamp)) + Expect(header.BlockHash).To(Equal(mocks.MockHeaderMetaData.BlockHash)) + Expect(header.ParentHash).To(Equal(mocks.MockHeaderMetaData.ParentHash)) + // check trxs were properly indexed + trxs := make([]btc.TxModel, 0) + pgStr = `SELECT transaction_cids.id, transaction_cids.header_id, transaction_cids.index, + transaction_cids.tx_hash, transaction_cids.cid, transaction_cids.segwit, transaction_cids.witness_hash + FROM btc.transaction_cids INNER JOIN btc.header_cids ON (transaction_cids.header_id = header_cids.id) + WHERE header_cids.block_number = $1` + err = db.Select(&trxs, pgStr, mocks.MockHeaderMetaData.BlockNumber) + Expect(err).ToNot(HaveOccurred()) + Expect(len(trxs)).To(Equal(3)) + for _, tx := range trxs { + Expect(tx.SegWit).To(Equal(false)) + Expect(tx.HeaderID).To(Equal(header.ID)) + Expect(tx.WitnessHash).To(Equal("")) + switch tx.Index { + case 0: + Expect(tx.CID).To(Equal("mockTrxCID1")) + Expect(tx.TxHash).To(Equal(mocks.MockBlock.Transactions[0].TxHash().String())) + case 1: + Expect(tx.CID).To(Equal("mockTrxCID2")) + Expect(tx.TxHash).To(Equal(mocks.MockBlock.Transactions[1].TxHash().String())) + case 2: + Expect(tx.CID).To(Equal("mockTrxCID3")) + Expect(tx.TxHash).To(Equal(mocks.MockBlock.Transactions[2].TxHash().String())) + } + } + }) + }) +}) diff --git a/pkg/super_node/btc/ipld_fetcher_test.go b/pkg/super_node/btc/ipld_fetcher_test.go new file mode 100644 index 00000000..8dd3c1ae --- /dev/null +++ b/pkg/super_node/btc/ipld_fetcher_test.go @@ -0,0 +1,17 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc diff --git a/pkg/super_node/btc/mocks/test_data.go b/pkg/super_node/btc/mocks/test_data.go new file mode 100644 index 00000000..cd6a3279 --- /dev/null +++ b/pkg/super_node/btc/mocks/test_data.go @@ -0,0 +1,718 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package mocks + +import ( + "strconv" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc" +) + +var ( + MockBlockHeight int32 = 1337 + MockBlock = wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: chainhash.Hash([32]byte{ // Make go vet happy. + 0x50, 0x12, 0x01, 0x19, 0x17, 0x2a, 0x61, 0x04, + 0x21, 0xa6, 0xc3, 0x01, 0x1d, 0xd3, 0x30, 0xd9, + 0xdf, 0x07, 0xb6, 0x36, 0x16, 0xc2, 0xcc, 0x1f, + 0x1c, 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + }), // 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250 + MerkleRoot: chainhash.Hash([32]byte{ // Make go vet happy. + 0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0, + 0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22, + 0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85, + 0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3, + }), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766 + Timestamp: time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC + Bits: 0x1b04864c, // 453281356 + Nonce: 0x10572b0f, // 274148111 + }, + Transactions: []*wire.MsgTx{ + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x12a05f200, // 5000000000 + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, + 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, + 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, + 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, + }), // 87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03 + Index: 0, + }, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, + 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, + 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, + 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, + 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, + 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, + 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, + 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, + 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, + 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, + 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, + 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, + 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, + 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, + 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, + 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, + 0xd3, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x2123e300, // 556000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x108e20f00, // 4444000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, + 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, + 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, + 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, + }), // cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3 + Index: 1, + }, + SignatureScript: []byte{ + 0x47, // OP_DATA_71 + 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, + 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, + 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, + 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, + 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, + 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, + 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, + 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, + 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, + 0x41, // OP_DATA_65 + 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, + 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, + 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, + 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, + 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, + 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, + 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, + 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, + 0x0f, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0xf4240, // 1000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x11d260c0, // 299000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + }, + } + MockTransactions = []*btcutil.Tx{ + btcutil.NewTx(MockBlock.Transactions[0]), + btcutil.NewTx(MockBlock.Transactions[1]), + btcutil.NewTx(MockBlock.Transactions[2]), + } + MockBlockPayload = btc.BlockPayload{ + Header: &MockBlock.Header, + Txs: MockTransactions, + Height: MockBlockHeight, + } + sClass1, addresses1, numOfSigs1, _ = txscript.ExtractPkScriptAddrs([]byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, &chaincfg.MainNetParams) + sClass2a, addresses2a, numOfSigs2a, _ = txscript.ExtractPkScriptAddrs([]byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, &chaincfg.MainNetParams) + sClass2b, addresses2b, numOfSigs2b, _ = txscript.ExtractPkScriptAddrs([]byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, &chaincfg.MainNetParams) + sClass3a, addresses3a, numOfSigs3a, _ = txscript.ExtractPkScriptAddrs([]byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, &chaincfg.MainNetParams) + sClass3b, addresses3b, numOfSigs3b, _ = txscript.ExtractPkScriptAddrs([]byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, &chaincfg.MainNetParams) + MockTxsMetaData = []btc.TxModelWithInsAndOuts{ + { + TxHash: MockBlock.Transactions[0].TxHash().String(), + Index: 0, + SegWit: MockBlock.Transactions[0].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + SignatureScript: []byte{ + 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, + }, + PreviousOutPointHash: chainhash.Hash{}.String(), + PreviousOutPointIndex: 0xffffffff, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Value: 5000000000, + Index: 0, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass1), + RequiredSigs: int64(numOfSigs1), + Addresses: stringSliceFromAddresses(addresses1), + }, + }, + }, + { + TxHash: MockBlock.Transactions[1].TxHash().String(), + Index: 1, + SegWit: MockBlock.Transactions[1].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + PreviousOutPointHash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, + 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, + 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, + 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, + }).String(), + PreviousOutPointIndex: 0, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, + 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, + 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, + 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, + 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, + 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, + 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, + 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, + 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, + 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, + 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, + 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, + 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, + 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, + 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, + 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, + 0xd3, // 65-byte pubkey + }, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Index: 0, + Value: 556000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass2a), + RequiredSigs: int64(numOfSigs2a), + Addresses: stringSliceFromAddresses(addresses2a), + }, + { + Index: 1, + Value: 4444000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass2b), + RequiredSigs: int64(numOfSigs2b), + Addresses: stringSliceFromAddresses(addresses2b), + }, + }, + }, + { + TxHash: MockBlock.Transactions[2].TxHash().String(), + Index: 2, + SegWit: MockBlock.Transactions[2].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + PreviousOutPointHash: chainhash.Hash([32]byte{ // Make go vet happy. + 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, + 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, + 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, + 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, + }).String(), + PreviousOutPointIndex: 1, + SignatureScript: []byte{ + 0x47, // OP_DATA_71 + 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, + 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, + 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, + 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, + 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, + 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, + 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, + 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, + 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, + 0x41, // OP_DATA_65 + 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, + 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, + 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, + 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, + 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, + 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, + 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, + 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, + 0x0f, // 65-byte pubkey + }, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Index: 0, + Value: 1000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass3a), + RequiredSigs: int64(numOfSigs3a), + Addresses: stringSliceFromAddresses(addresses3a), + }, + { + Index: 1, + Value: 299000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass3b), + RequiredSigs: int64(numOfSigs3b), + Addresses: stringSliceFromAddresses(addresses3b), + }, + }, + }, + } + MockTxsMetaDataPostPublish = []btc.TxModelWithInsAndOuts{ + { + CID: "mockTrxCID1", + TxHash: MockBlock.Transactions[0].TxHash().String(), + Index: 0, + SegWit: MockBlock.Transactions[0].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + SignatureScript: []byte{ + 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, + }, + PreviousOutPointHash: chainhash.Hash{}.String(), + PreviousOutPointIndex: 0xffffffff, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Value: 5000000000, + Index: 0, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass1), + RequiredSigs: int64(numOfSigs1), + Addresses: stringSliceFromAddresses(addresses1), + }, + }, + }, + { + CID: "mockTrxCID2", + TxHash: MockBlock.Transactions[1].TxHash().String(), + Index: 1, + SegWit: MockBlock.Transactions[1].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + PreviousOutPointHash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, + 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, + 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, + 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, + }).String(), + PreviousOutPointIndex: 0, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, + 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, + 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, + 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, + 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, + 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, + 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, + 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, + 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, + 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, + 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, + 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, + 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, + 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, + 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, + 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, + 0xd3, // 65-byte pubkey + }, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Index: 0, + Value: 556000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass2a), + RequiredSigs: int64(numOfSigs2a), + Addresses: stringSliceFromAddresses(addresses2a), + }, + { + Index: 1, + Value: 4444000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass2b), + RequiredSigs: int64(numOfSigs2b), + Addresses: stringSliceFromAddresses(addresses2b), + }, + }, + }, + { + CID: "mockTrxCID3", + TxHash: MockBlock.Transactions[2].TxHash().String(), + Index: 2, + SegWit: MockBlock.Transactions[2].HasWitness(), + TxInputs: []btc.TxInput{ + { + Index: 0, + PreviousOutPointHash: chainhash.Hash([32]byte{ // Make go vet happy. + 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, + 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, + 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, + 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, + }).String(), + PreviousOutPointIndex: 1, + SignatureScript: []byte{ + 0x47, // OP_DATA_71 + 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, + 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, + 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, + 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, + 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, + 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, + 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, + 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, + 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, + 0x41, // OP_DATA_65 + 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, + 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, + 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, + 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, + 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, + 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, + 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, + 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, + 0x0f, // 65-byte pubkey + }, + }, + }, + TxOutputs: []btc.TxOutput{ + { + Index: 0, + Value: 1000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass3a), + RequiredSigs: int64(numOfSigs3a), + Addresses: stringSliceFromAddresses(addresses3a), + }, + { + Index: 1, + Value: 299000000, + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + ScriptClass: uint8(sClass3b), + RequiredSigs: int64(numOfSigs3b), + Addresses: stringSliceFromAddresses(addresses3b), + }, + }, + }, + } + MockHeaderMetaData = btc.HeaderModel{ + CID: "mockHeaderCID", + ParentHash: MockBlock.Header.PrevBlock.String(), + BlockNumber: strconv.Itoa(int(MockBlockHeight)), + BlockHash: MockBlock.Header.BlockHash().String(), + Timestamp: MockBlock.Header.Timestamp.UnixNano(), + Bits: MockBlock.Header.Bits, + } + MockIPLDPayload = btc.IPLDPayload{ + BlockPayload: MockBlockPayload, + TxMetaData: MockTxsMetaData, + } + MockCIDPayload = btc.CIDPayload{ + HeaderCID: MockHeaderMetaData, + TransactionCIDs: MockTxsMetaDataPostPublish, + } + DummyCIDPayloadForFKReference = btc.CIDPayload{ + HeaderCID: btc.HeaderModel{ + CID: "dummyHeader", + ParentHash: "", + BlockHash: "", + BlockNumber: "1336", + Bits: 1, + Timestamp: 1000000000, + }, + TransactionCIDs: []btc.TxModelWithInsAndOuts{ + { + TxHash: "87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03", + CID: "dummyTx1", + Index: 0, + }, + { + TxHash: "cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3", + CID: "dummyTx2", + Index: 1, + }, + }, + } +) + +func stringSliceFromAddresses(addrs []btcutil.Address) []string { + strs := make([]string, len(addrs)) + for i, addr := range addrs { + strs[i] = addr.EncodeAddress() + } + return strs +} diff --git a/pkg/super_node/btc/models.go b/pkg/super_node/btc/models.go index 580dfc9e..7b3a93f6 100644 --- a/pkg/super_node/btc/models.go +++ b/pkg/super_node/btc/models.go @@ -25,7 +25,6 @@ type HeaderModel struct { BlockHash string `db:"block_hash"` ParentHash string `db:"parent_hash"` CID string `db:"cid"` - Version int32 `db:"version"` Timestamp int64 `db:"timestamp"` Bits uint32 `db:"bits"` } @@ -61,9 +60,8 @@ type TxInput struct { Index int64 `db:"index"` TxWitness [][]byte `db:"witness"` SignatureScript []byte `db:"sig_script"` - PreviousOutPointTxID int64 `db:"outpoint_tx_id"` PreviousOutPointIndex uint32 `db:"outpoint_index"` - PreviousOutPointHash string + PreviousOutPointHash string `db:"outpoint_tx_hash"` } // TxOutput is the db model for btc.tx_outputs table diff --git a/pkg/super_node/btc/payload_fetcher_test.go b/pkg/super_node/btc/payload_fetcher_test.go new file mode 100644 index 00000000..8dd3c1ae --- /dev/null +++ b/pkg/super_node/btc/payload_fetcher_test.go @@ -0,0 +1,17 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc diff --git a/pkg/super_node/btc/publisher.go b/pkg/super_node/btc/publisher.go index 3b391bee..f010217c 100644 --- a/pkg/super_node/btc/publisher.go +++ b/pkg/super_node/btc/publisher.go @@ -63,7 +63,6 @@ func (pub *IPLDPublisher) Publish(payload shared.StreamedIPLDs) (shared.CIDsForI ParentHash: ipldPayload.Header.PrevBlock.String(), BlockNumber: strconv.Itoa(int(ipldPayload.Height)), BlockHash: ipldPayload.Header.BlockHash().String(), - Version: ipldPayload.Header.Version, Timestamp: ipldPayload.Header.Timestamp.UnixNano(), Bits: ipldPayload.Header.Bits, } diff --git a/pkg/super_node/btc/publisher_test.go b/pkg/super_node/btc/publisher_test.go new file mode 100644 index 00000000..fee15d57 --- /dev/null +++ b/pkg/super_node/btc/publisher_test.go @@ -0,0 +1,56 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + mocks2 "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc" + "github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks" +) + +var ( + mockHeaderDagPutter *mocks2.DagPutter + mockTrxDagPutter *mocks2.DagPutter +) + +var _ = Describe("Publisher", func() { + BeforeEach(func() { + mockHeaderDagPutter = new(mocks2.DagPutter) + mockTrxDagPutter = new(mocks2.DagPutter) + }) + + Describe("Publish", func() { + It("Publishes the passed IPLDPayload objects to IPFS and returns a CIDPayload for indexing", func() { + mockHeaderDagPutter.CIDsToReturn = []string{"mockHeaderCID"} + mockTrxDagPutter.CIDsToReturn = []string{"mockTrxCID1", "mockTrxCID2", "mockTrxCID3"} + publisher := btc.IPLDPublisher{ + HeaderPutter: mockHeaderDagPutter, + TransactionPutter: mockTrxDagPutter, + } + payload, err := publisher.Publish(mocks.MockIPLDPayload) + Expect(err).ToNot(HaveOccurred()) + cidPayload, ok := payload.(*btc.CIDPayload) + Expect(ok).To(BeTrue()) + Expect(cidPayload).To(Equal(&mocks.MockCIDPayload)) + Expect(cidPayload.HeaderCID).To(Equal(mocks.MockHeaderMetaData)) + Expect(cidPayload.TransactionCIDs).To(Equal(mocks.MockTxsMetaDataPostPublish)) + }) + }) +}) diff --git a/pkg/super_node/btc/resolver_test.go b/pkg/super_node/btc/resolver_test.go new file mode 100644 index 00000000..8dd3c1ae --- /dev/null +++ b/pkg/super_node/btc/resolver_test.go @@ -0,0 +1,17 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc diff --git a/pkg/super_node/btc/retriever.go b/pkg/super_node/btc/retriever.go index 6a285d91..3e019a75 100644 --- a/pkg/super_node/btc/retriever.go +++ b/pkg/super_node/btc/retriever.go @@ -20,6 +20,8 @@ import ( "fmt" "math/big" + "github.com/lib/pq" + "github.com/ethereum/go-ethereum/common" "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" @@ -58,7 +60,7 @@ func (ecr *CIDRetriever) RetrieveLastBlockNumber() (int64, error) { func (ecr *CIDRetriever) Retrieve(filter shared.SubscriptionSettings, blockNumber int64) (shared.CIDsForFetching, bool, error) { streamFilter, ok := filter.(*SubscriptionSettings) if !ok { - return nil, true, fmt.Errorf("eth retriever expected filter type %T got %T", &SubscriptionSettings{}, filter) + return nil, true, fmt.Errorf("btc retriever expected filter type %T got %T", &SubscriptionSettings{}, filter) } log.Debug("retrieving cids") tx, err := ecr.db.Beginx() @@ -90,10 +92,6 @@ func (ecr *CIDRetriever) Retrieve(filter shared.SubscriptionSettings, blockNumbe return nil, true, err } } - trxIds := make([]int64, 0, len(cw.Transactions)) - for _, tx := range cw.Transactions { - trxIds = append(trxIds, tx.ID) - } return cw, empty(cw), tx.Commit() } @@ -113,18 +111,70 @@ func (ecr *CIDRetriever) RetrieveHeaderCIDs(tx *sqlx.Tx, blockNumber int64) ([]H return headers, tx.Select(&headers, pgStr, blockNumber) } +/* +type TxModel struct { + ID int64 `db:"id"` + HeaderID int64 `db:"header_id"` + Index int64 `db:"index"` + TxHash string `db:"tx_hash"` + CID string `db:"cid"` + SegWit bool `db:"segwit"` + WitnessHash string `db:"witness_hash"` +} +// TxFilter contains filter settings for txs +type TxFilter struct { + Off bool + Index int64 // allow filtering by index so that we can filter for only coinbase transactions (index 0) if we want to + Segwit bool // allow filtering for segwit trxs + WitnessHashes []string // allow filtering for specific witness hashes + PkScriptClass uint8 // allow filtering for txs that have at least one tx output with the specified pkscript class + MultiSig bool // allow filtering for txs that have at least one tx output that requires more than one signature + Addresses []string // allow filtering for txs that have at least one tx output with at least one of the provided addresses +} +*/ + // RetrieveTxCIDs retrieves and returns all of the trx cids at the provided blockheight that conform to the provided filter parameters // also returns the ids for the returned transaction cids func (ecr *CIDRetriever) RetrieveTxCIDs(tx *sqlx.Tx, txFilter TxFilter, blockNumber int64) ([]TxModel, error) { log.Debug("retrieving transaction cids for block ", blockNumber) args := make([]interface{}, 0, 3) results := make([]TxModel, 0) - pgStr := `SELECT transaction_cids.id, transaction_cids.header_id, + id := 1 + pgStr := fmt.Sprintf(`SELECT transaction_cids.id, transaction_cids.header_id, transaction_cids.tx_hash, transaction_cids.cid, - transaction_cids.dst, transaction_cids.src, transaction_cids.index - FROM eth.transaction_cids INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id) - WHERE header_cids.block_number = $1` + transaction_cids.segwit, transaction_cids.witness_hash, transaction_cids.index + FROM btc.transaction_cids, btc.header_cids, btc.tx_inputs, btc.tx_outputs + WHERE transaction_cids.header_id = header_cids.id + AND tx_inputs.tx_id = transaction_cids.id + AND tx_outputs.tx_id = transaction_cids.id + AND header_cids.block_number = $%d`, id) args = append(args, blockNumber) + id++ + if txFilter.Segwit { + pgStr += ` AND transaction_cids.segwit = true` + } + if txFilter.MultiSig { + pgStr += ` AND tx_outputs.required_sigs > 1` + } + if len(txFilter.WitnessHashes) > 0 { + pgStr += fmt.Sprintf(` AND transaction_cids.witness_hash = ANY($%d::VARCHAR(66)[])`, id) + args = append(args, pq.Array(txFilter.WitnessHashes)) + id++ + } + if len(txFilter.Addresses) > 0 { + pgStr += fmt.Sprintf(` AND tx_outputs.addresses && $%d::VARCHAR(66)[]`, id) + args = append(args, pq.Array(txFilter.Addresses)) + id++ + } + if len(txFilter.Indexes) > 0 { + pgStr += fmt.Sprintf(` AND transaction_cids.index = ANY($%d::INTEGER[])`, id) + args = append(args, pq.Array(txFilter.Indexes)) + id++ + } + if len(txFilter.PkScriptClasses) > 0 { + pgStr += fmt.Sprintf(` AND tx_outputs.script_class = ANY($%d::INTEGER[])`, id) + args = append(args, pq.Array(txFilter.PkScriptClasses)) + } return results, tx.Select(&results, pgStr, args...) } diff --git a/pkg/super_node/btc/retriever_test.go b/pkg/super_node/btc/retriever_test.go new file mode 100644 index 00000000..8dd3c1ae --- /dev/null +++ b/pkg/super_node/btc/retriever_test.go @@ -0,0 +1,17 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc diff --git a/pkg/super_node/btc/subscription_config.go b/pkg/super_node/btc/subscription_config.go index f95e3036..985ea715 100644 --- a/pkg/super_node/btc/subscription_config.go +++ b/pkg/super_node/btc/subscription_config.go @@ -17,6 +17,7 @@ package btc import ( + "errors" "math/big" "github.com/spf13/viper" @@ -24,29 +25,6 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" ) -/* -// HeaderModel is the db model for btc.header_cids table -// TxInput is the db model for btc.tx_inputs table -type TxInput struct { - ID int64 `db:"id"` - TxID int64 `db:"tx_id"` - Index int64 `db:"index"` - TxWitness [][]byte `db:"tx_witness"` - SignatureScript []byte `db:"sig_script"` - PreviousOutPointHash string `db:"outpoint_hash"` - PreviousOutPointIndex uint32 `db:"outpoint_index"` -} - -// TxOutput is the db model for btc.tx_outputs table -type TxOutput struct { - ID int64 `db:"id"` - TxID int64 `db:"tx_id"` - Index int64 `db:"index"` - Value int64 `db:"value"` - PkScript []byte `db:"pk_script"` -} - -*/ // SubscriptionSettings config is used by a subscriber to specify what bitcoin data to stream from the super node type SubscriptionSettings struct { BackFill bool @@ -64,13 +42,13 @@ type HeaderFilter struct { // TxFilter contains filter settings for txs type TxFilter struct { - Off bool - // Top level trx filters - Index int64 // allow filtering by index so that we can filter for only coinbase transactions (index 0) if we want to - Segwit bool // allow filtering for segwit trxs - WitnessHashes []string // allow filtering for specific witness hashes - // TODO: trx input filters - // TODO: trx output filters + Off bool + Segwit bool // allow filtering for segwit trxs + WitnessHashes []string // allow filtering for specific witness hashes + Indexes []int64 // allow filtering for specific transaction indexes (e.g. 0 for coinbase transactions) + PkScriptClasses []uint8 // allow filtering for txs that have at least one tx output with the specified pkscript class + MultiSig bool // allow filtering for txs that have at least one tx output that requires more than one signature + Addresses []string // allow filtering for txs that have at least one tx output with at least one of the provided addresses } // Init is used to initialize a EthSubscription struct with env variables @@ -83,17 +61,30 @@ func NewEthSubscriptionConfig() (*SubscriptionSettings, error) { // 0 start means we start at the beginning and 0 end means we continue indefinitely sc.Start = big.NewInt(viper.GetInt64("superNode.btcSubscription.startingBlock")) sc.End = big.NewInt(viper.GetInt64("superNode.btcSubscription.endingBlock")) - // Below default to false, which means we get all headers and no uncles by default + // Below default to false, which means we get all headers by default sc.HeaderFilter = HeaderFilter{ Off: viper.GetBool("superNode.btcSubscription.headerFilter.off"), } // Below defaults to false and two slices of length 0 // Which means we get all transactions by default + pksc := viper.Get("superNode.btcSubscription.txFilter.pkScriptClass") + pkScriptClasses, ok := pksc.([]uint8) + if !ok { + return nil, errors.New("superNode.btcSubscription.txFilter.pkScriptClass needs to be an array of uint8s") + } + is := viper.Get("superNode.btcSubscription.txFilter.indexes") + indexes, ok := is.([]int64) + if !ok { + return nil, errors.New("superNode.btcSubscription.txFilter.indexes needs to be an array of int64s") + } sc.TxFilter = TxFilter{ - Off: viper.GetBool("superNode.btcSubscription.txFilter.off"), - Index: viper.GetInt64("superNode.btcSubscription.txFilter.index"), - Segwit: viper.GetBool("superNode.btcSubscription.txFilter.segwit"), - WitnessHashes: viper.GetStringSlice("superNode.btcSubscription.txFilter.witnessHashes"), + Off: viper.GetBool("superNode.btcSubscription.txFilter.off"), + Segwit: viper.GetBool("superNode.btcSubscription.txFilter.segwit"), + WitnessHashes: viper.GetStringSlice("superNode.btcSubscription.txFilter.witnessHashes"), + PkScriptClasses: pkScriptClasses, + Indexes: indexes, + MultiSig: viper.GetBool("superNode.btcSubscription.txFilter.multiSig"), + Addresses: viper.GetStringSlice("superNode.btcSubscription.txFilter.addresses"), } return sc, nil } diff --git a/pkg/super_node/btc/test_helpers.go b/pkg/super_node/btc/test_helpers.go new file mode 100644 index 00000000..7e431af6 --- /dev/null +++ b/pkg/super_node/btc/test_helpers.go @@ -0,0 +1,43 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package btc + +import ( + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres" +) + +// TearDownDB is used to tear down the super node dbs after tests +func TearDownDB(db *postgres.DB) { + tx, err := db.Beginx() + Expect(err).NotTo(HaveOccurred()) + + _, err = tx.Exec(`DELETE FROM btc.header_cids`) + Expect(err).NotTo(HaveOccurred()) + _, err = tx.Exec(`DELETE FROM btc.transaction_cids`) + Expect(err).NotTo(HaveOccurred()) + _, err = tx.Exec(`DELETE FROM btc.tx_inputs`) + Expect(err).NotTo(HaveOccurred()) + _, err = tx.Exec(`DELETE FROM btc.tx_outputs`) + Expect(err).NotTo(HaveOccurred()) + _, err = tx.Exec(`DELETE FROM blocks`) + Expect(err).NotTo(HaveOccurred()) + + err = tx.Commit() + Expect(err).NotTo(HaveOccurred()) +} diff --git a/pkg/super_node/eth/indexer_test.go b/pkg/super_node/eth/indexer_test.go index 65853e1c..e176f81d 100644 --- a/pkg/super_node/eth/indexer_test.go +++ b/pkg/super_node/eth/indexer_test.go @@ -19,24 +19,23 @@ package eth_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" "github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/super_node/eth" - eth2 "github.com/vulcanize/vulcanizedb/pkg/super_node/eth" "github.com/vulcanize/vulcanizedb/pkg/super_node/eth/mocks" + "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" ) var _ = Describe("Indexer", func() { var ( db *postgres.DB err error - repo *eth2.CIDIndexer + repo *eth.CIDIndexer ) BeforeEach(func() { - db, err = eth.SetupDB() + db, err = shared.SetupDB() Expect(err).ToNot(HaveOccurred()) - repo = eth2.NewCIDIndexer(db) + repo = eth.NewCIDIndexer(db) }) AfterEach(func() { eth.TearDownDB(db) diff --git a/pkg/super_node/eth/resolver.go b/pkg/super_node/eth/resolver.go index 801759ce..acc038ff 100644 --- a/pkg/super_node/eth/resolver.go +++ b/pkg/super_node/eth/resolver.go @@ -41,48 +41,24 @@ func (eir *IPLDResolver) Resolve(iplds shared.FetchedIPLDs) (shared.ServerRespon } return StreamResponse{ BlockNumber: ipfsBlocks.BlockNumber, - HeadersRlp: eir.ResolveHeaders(ipfsBlocks.Headers), - UnclesRlp: eir.ResolveUncles(ipfsBlocks.Uncles), - TransactionsRlp: eir.ResolveTransactions(ipfsBlocks.Transactions), - ReceiptsRlp: eir.ResolveReceipts(ipfsBlocks.Receipts), - StateNodesRlp: eir.ResolveState(ipfsBlocks.StateNodes), - StorageNodesRlp: eir.ResolveStorage(ipfsBlocks.StorageNodes), + HeadersRlp: eir.resolve(ipfsBlocks.Headers), + UnclesRlp: eir.resolve(ipfsBlocks.Uncles), + TransactionsRlp: eir.resolve(ipfsBlocks.Transactions), + ReceiptsRlp: eir.resolve(ipfsBlocks.Receipts), + StateNodesRlp: eir.resolveState(ipfsBlocks.StateNodes), + StorageNodesRlp: eir.resolveStorage(ipfsBlocks.StorageNodes), }, nil } -func (eir *IPLDResolver) ResolveHeaders(iplds []blocks.Block) [][]byte { - headerRlps := make([][]byte, 0, len(iplds)) +func (eir *IPLDResolver) resolve(iplds []blocks.Block) [][]byte { + rlps := make([][]byte, 0, len(iplds)) for _, ipld := range iplds { - headerRlps = append(headerRlps, ipld.RawData()) + rlps = append(rlps, ipld.RawData()) } - return headerRlps + return rlps } -func (eir *IPLDResolver) ResolveUncles(iplds []blocks.Block) [][]byte { - uncleRlps := make([][]byte, 0, len(iplds)) - for _, ipld := range iplds { - uncleRlps = append(uncleRlps, ipld.RawData()) - } - return uncleRlps -} - -func (eir *IPLDResolver) ResolveTransactions(iplds []blocks.Block) [][]byte { - trxs := make([][]byte, 0, len(iplds)) - for _, ipld := range iplds { - trxs = append(trxs, ipld.RawData()) - } - return trxs -} - -func (eir *IPLDResolver) ResolveReceipts(iplds []blocks.Block) [][]byte { - rcts := make([][]byte, 0, len(iplds)) - for _, ipld := range iplds { - rcts = append(rcts, ipld.RawData()) - } - return rcts -} - -func (eir *IPLDResolver) ResolveState(iplds map[common.Hash]blocks.Block) map[common.Hash][]byte { +func (eir *IPLDResolver) resolveState(iplds map[common.Hash]blocks.Block) map[common.Hash][]byte { stateNodes := make(map[common.Hash][]byte, len(iplds)) for key, ipld := range iplds { stateNodes[key] = ipld.RawData() @@ -90,7 +66,7 @@ func (eir *IPLDResolver) ResolveState(iplds map[common.Hash]blocks.Block) map[co return stateNodes } -func (eir *IPLDResolver) ResolveStorage(iplds map[common.Hash]map[common.Hash]blocks.Block) map[common.Hash]map[common.Hash][]byte { +func (eir *IPLDResolver) resolveStorage(iplds map[common.Hash]map[common.Hash]blocks.Block) map[common.Hash]map[common.Hash][]byte { storageNodes := make(map[common.Hash]map[common.Hash][]byte) for stateKey, storageIPLDs := range iplds { storageNodes[stateKey] = make(map[common.Hash][]byte) diff --git a/pkg/super_node/eth/retriever.go b/pkg/super_node/eth/retriever.go index 12552c16..ca7c2e81 100644 --- a/pkg/super_node/eth/retriever.go +++ b/pkg/super_node/eth/retriever.go @@ -176,18 +176,21 @@ func (ecr *CIDRetriever) RetrieveTxCIDs(tx *sqlx.Tx, txFilter TxFilter, blockNum log.Debug("retrieving transaction cids for block ", blockNumber) args := make([]interface{}, 0, 3) results := make([]TxModel, 0) - pgStr := `SELECT transaction_cids.id, transaction_cids.header_id, + id := 1 + pgStr := fmt.Sprintf(`SELECT transaction_cids.id, transaction_cids.header_id, transaction_cids.tx_hash, transaction_cids.cid, transaction_cids.dst, transaction_cids.src, transaction_cids.index FROM eth.transaction_cids INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id) - WHERE header_cids.block_number = $1` + WHERE header_cids.block_number = $%d`, id) args = append(args, blockNumber) + id++ if len(txFilter.Dst) > 0 { - pgStr += ` AND transaction_cids.dst = ANY($2::VARCHAR(66)[])` + pgStr += fmt.Sprintf(` AND transaction_cids.dst = ANY($%d::VARCHAR(66)[])`, id) args = append(args, pq.Array(txFilter.Dst)) + id++ } if len(txFilter.Src) > 0 { - pgStr += ` AND transaction_cids.src = ANY($3::VARCHAR(66)[])` + pgStr += fmt.Sprintf(` AND transaction_cids.src = ANY($%d::VARCHAR(66)[])`, id) args = append(args, pq.Array(txFilter.Src)) } return results, tx.Select(&results, pgStr, args...) @@ -309,26 +312,26 @@ func (ecr *CIDRetriever) RetrieveStateCIDs(tx *sqlx.Tx, stateFilter StateFilter, func (ecr *CIDRetriever) RetrieveStorageCIDs(tx *sqlx.Tx, storageFilter StorageFilter, blockNumber int64) ([]StorageNodeWithStateKeyModel, error) { log.Debug("retrieving storage cids for block ", blockNumber) args := make([]interface{}, 0, 3) - pgStr := `SELECT storage_cids.id, storage_cids.state_id, storage_cids.storage_key, + id := 1 + pgStr := fmt.Sprintf(`SELECT storage_cids.id, storage_cids.state_id, storage_cids.storage_key, storage_cids.leaf, storage_cids.cid, state_cids.state_key FROM eth.storage_cids, eth.state_cids, eth.header_cids WHERE storage_cids.state_id = state_cids.id AND state_cids.header_id = header_cids.id - AND header_cids.block_number = $1` + AND header_cids.block_number = $%d`, id) args = append(args, blockNumber) + id++ addrLen := len(storageFilter.Addresses) if addrLen > 0 { keys := make([]string, addrLen) for i, addr := range storageFilter.Addresses { keys[i] = crypto.Keccak256Hash(common.HexToAddress(addr).Bytes()).String() } - pgStr += ` AND state_cids.state_key = ANY($2::VARCHAR(66)[])` + pgStr += fmt.Sprintf(` AND state_cids.state_key = ANY($%d::VARCHAR(66)[])`, id) args = append(args, pq.Array(keys)) - if len(storageFilter.StorageKeys) > 0 { - pgStr += ` AND storage_cids.storage_key = ANY($3::VARCHAR(66)[])` - args = append(args, pq.Array(storageFilter.StorageKeys)) - } - } else if len(storageFilter.StorageKeys) > 0 { - pgStr += ` AND storage_cids.storage_key = ANY($2::VARCHAR(66)[])` + id++ + } + if len(storageFilter.StorageKeys) > 0 { + pgStr += fmt.Sprintf(` AND storage_cids.storage_key = ANY($%d::VARCHAR(66)[])`, id) args = append(args, pq.Array(storageFilter.StorageKeys)) } if !storageFilter.IntermediateNodes { diff --git a/pkg/super_node/eth/retriever_test.go b/pkg/super_node/eth/retriever_test.go index 17c1da37..f5d3adc1 100644 --- a/pkg/super_node/eth/retriever_test.go +++ b/pkg/super_node/eth/retriever_test.go @@ -212,7 +212,7 @@ var _ = Describe("Retriever", func() { ) BeforeEach(func() { var err error - db, err = eth.SetupDB() + db, err = shared.SetupDB() Expect(err).ToNot(HaveOccurred()) repo = eth2.NewCIDIndexer(db) retriever = eth2.NewCIDRetriever(db) diff --git a/pkg/super_node/eth/test_helpers.go b/pkg/super_node/eth/test_helpers.go index 58c65064..d224e028 100644 --- a/pkg/super_node/eth/test_helpers.go +++ b/pkg/super_node/eth/test_helpers.go @@ -19,20 +19,9 @@ package eth import ( . "github.com/onsi/gomega" - "github.com/vulcanize/vulcanizedb/pkg/config" - "github.com/vulcanize/vulcanizedb/pkg/eth/core" "github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres" ) -// SetupDB is use to setup a db for super node tests -func SetupDB() (*postgres.DB, error) { - return postgres.NewDB(config.Database{ - Hostname: "localhost", - Name: "vulcanize_testing", - Port: 5432, - }, core.Node{}) -} - // TearDownDB is used to tear down the super node dbs after tests func TearDownDB(db *postgres.DB) { tx, err := db.Beginx() diff --git a/pkg/super_node/shared/functions.go b/pkg/super_node/shared/functions.go index 9b21409a..dc941440 100644 --- a/pkg/super_node/shared/functions.go +++ b/pkg/super_node/shared/functions.go @@ -18,7 +18,6 @@ package shared import ( "bytes" - "reflect" ) // ListContainsString used to check if a list of strings contains a particular string @@ -50,8 +49,3 @@ func ListContainsGap(gapList []Gap, gap Gap) bool { } return false } - -// IsPointer returns true if the concrete type underneath the provided interface is a pointer -func IsPointer(i interface{}) bool { - return reflect.ValueOf(i).Type().Kind() == reflect.Ptr -} diff --git a/pkg/super_node/shared/test_helpers.go b/pkg/super_node/shared/test_helpers.go new file mode 100644 index 00000000..1b2ee606 --- /dev/null +++ b/pkg/super_node/shared/test_helpers.go @@ -0,0 +1,32 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package shared + +import ( + "github.com/vulcanize/vulcanizedb/pkg/config" + "github.com/vulcanize/vulcanizedb/pkg/eth/core" + "github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres" +) + +// SetupDB is use to setup a db for super node tests +func SetupDB() (*postgres.DB, error) { + return postgres.NewDB(config.Database{ + Hostname: "localhost", + Name: "vulcanize_testing", + Port: 5432, + }, core.Node{}) +}