├── settings.conf ├── .gitignore ├── examples └── zmq │ └── main.go ├── .vscode ├── settings.json └── launch.json ├── go.mod ├── scratch_test.go ├── logger.go ├── bitIndexClient.go ├── README.md ├── End2End_test.go ├── go.sum ├── zmq.go ├── scratch.go ├── rpcClient.go ├── LICENSE ├── RPC_test.go ├── JSON.go └── RPC.go /settings.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .history -------------------------------------------------------------------------------- /examples/zmq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/ordishs/go-bitcoin" 7 | ) 8 | 9 | func main() { 10 | zmq := bitcoin.NewZMQ("localhost", 28332) 11 | 12 | ch := make(chan []string) 13 | 14 | go func() { 15 | for c := range ch { 16 | log.Printf("%v", c) 17 | } 18 | }() 19 | 20 | if err := zmq.Subscribe("hashtx", ch); err != nil { 21 | log.Fatalf("%v", err) 22 | } 23 | 24 | if err := zmq.Subscribe("hashblock", ch); err != nil { 25 | log.Fatalf("%v", err) 26 | } 27 | 28 | waitCh := make(chan bool) 29 | <-waitCh 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "currentblocktx", 4 | "discardedfrommempool", 5 | "Divf", 6 | "Dont", 7 | "dontcheckfee", 8 | "excessutxocharge", 9 | "Gefi", 10 | "getblockbyheight", 11 | "getchaintxstats", 12 | "gettxout", 13 | "hashblock", 14 | "hashtx", 15 | "invalidtx", 16 | "Merkle", 17 | "merkleroot", 18 | "networkhashps", 19 | "ordish", 20 | "patrickmn", 21 | "paytxfee", 22 | "rawblock", 23 | "rawtx", 24 | "removedfrommempoolblock", 25 | "Segwit", 26 | "Sigs", 27 | "totalbytesrecv", 28 | "txcount", 29 | "txnpropagationfreq", 30 | "txnpropagationqlen", 31 | "zeromq" 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}" 13 | }, 14 | { 15 | "name": "Example", 16 | "type": "go", 17 | "request": "launch", 18 | "mode": "auto", 19 | "program": "${workspaceFolder}/examples/zmq/main.go" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ordishs/go-bitcoin 2 | 3 | go 1.24 4 | 5 | require ( 6 | bitbucket.org/simon_ordish/cryptolib v1.0.48 7 | github.com/go-zeromq/zmq4 v0.13.0 8 | github.com/libsv/go-bk v0.1.6 9 | github.com/libsv/go-bt/v2 v2.2.5 10 | github.com/patrickmn/go-cache v2.1.0+incompatible 11 | github.com/stretchr/testify v1.8.4 12 | golang.org/x/sync v0.3.0 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/kr/text v0.2.0 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/rogpeppe/go-internal v1.14.1 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | 24 | require ( 25 | github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 // indirect 26 | github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 // indirect 27 | github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect 28 | golang.org/x/crypto v0.13.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /scratch_test.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | func TestCoinbase(t *testing.T) { 9 | h, _ := hex.DecodeString("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580320a107152f5669614254432f48656c6c6f20576f726c64212f2cfabe6d6dbcbb1b0222e1aeebaca2a9c905bb23a3ad0302898ec600a9033a87ec1645a446010000000000000010f829ba0b13a84def80c389cde9840000ffffffff0174fdaf4a000000001976a914f1c075a01882ae0972f95d3a4177c86c852b7d9188ac00000000") 10 | 11 | tx, _ := TransactionFromBytes(h) 12 | 13 | expected := "5ebaa53d24c8246c439ccd9f142cbe93fc59582e7013733954120e9baab201df" 14 | 15 | if tx.Hash != expected { 16 | t.Errorf("Expected %q, got %q", expected, tx.Hash) 17 | } 18 | } 19 | 20 | func TestToHex(t *testing.T) { 21 | hexStr := "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580320a107152f5669614254432f48656c6c6f20576f726c64212f2cfabe6d6dbcbb1b0222e1aeebaca2a9c905bb23a3ad0302898ec600a9033a87ec1645a446010000000000000010f829ba0b13a84def80c389cde9840000ffffffff0174fdaf4a000000001976a914f1c075a01882ae0972f95d3a4177c86c852b7d9188ac00000000" 22 | h, _ := hex.DecodeString(hexStr) 23 | 24 | tx, _ := TransactionFromBytes(h) 25 | 26 | res := hex.EncodeToString(tx.ToHex()) 27 | 28 | if res != hexStr { 29 | t.Errorf("Expected %q, got %q", hexStr, res) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | type Logger interface { 9 | // Debugf logs a message at debug level. 10 | Debugf(format string, args ...interface{}) 11 | // Infof logs a message at info level. 12 | Infof(format string, args ...interface{}) 13 | // Warnf logs a message at warn level. 14 | Warnf(format string, args ...interface{}) 15 | // Errorf logs a message at error level. 16 | Errorf(format string, args ...interface{}) 17 | // Fatalf logs a message at fatal level. 18 | Fatalf(format string, args ...interface{}) 19 | } 20 | 21 | type DefaultLogger struct{} 22 | 23 | func (l *DefaultLogger) Debugf(format string, args ...interface{}) { 24 | f := fmt.Sprintf("DEBUG: %s", format) 25 | log.Printf(f, args...) 26 | } 27 | 28 | func (l *DefaultLogger) Infof(format string, args ...interface{}) { 29 | f := fmt.Sprintf("INFO: %s", format) 30 | log.Printf(f, args...) 31 | } 32 | 33 | func (l *DefaultLogger) Warnf(format string, args ...interface{}) { 34 | f := fmt.Sprintf("wARN: %s", format) 35 | log.Printf(f, args...) 36 | } 37 | 38 | func (l *DefaultLogger) Errorf(format string, args ...interface{}) { 39 | f := fmt.Sprintf("ERROR: %s", format) 40 | log.Printf(f, args...) 41 | } 42 | 43 | func (l *DefaultLogger) Fatalf(format string, args ...interface{}) { 44 | f := fmt.Sprintf("FATAL: %s", format) 45 | log.Printf(f, args...) 46 | } 47 | -------------------------------------------------------------------------------- /bitIndexClient.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | // Utxo bitindex comment 11 | type Utxo struct { 12 | TxID string `json:"txid"` 13 | Vout uint32 `json:"vout"` 14 | Height uint32 `json:"height"` 15 | Value uint64 `json:"value"` 16 | } 17 | 18 | // UtxoResponse comment 19 | type UtxoResponse struct { 20 | Address string `json:"address"` 21 | Utxos []Utxo `json:"utxos"` 22 | Balance uint64 `json:"balance"` 23 | } 24 | 25 | type bitIndexResponseData struct { 26 | Data UtxoResponse `json:"data"` 27 | } 28 | 29 | // BitIndex comment 30 | type BitIndex struct { 31 | BaseURL string 32 | } 33 | 34 | // NewBitIndexClient returns a new bitIndex client for the given url 35 | func NewBitIndexClient(url string) (*BitIndex, error) { 36 | return &BitIndex{ 37 | BaseURL: url, 38 | }, nil 39 | } 40 | 41 | // GetUtxos comment 42 | func (b *BitIndex) GetUtxos(addr string) (*UtxoResponse, error) { 43 | bitindexURL := fmt.Sprintf("%s/%s/%s", b.BaseURL, "utxos", addr) 44 | 45 | resp, err := http.Get(bitindexURL) 46 | if err != nil { 47 | return nil, err 48 | } 49 | defer resp.Body.Close() 50 | 51 | body, err := io.ReadAll(resp.Body) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | bir := bitIndexResponseData{} 57 | err = json.Unmarshal(body, &bir) 58 | if err != nil { 59 | fmt.Printf("error unarshalling body %+v", err) 60 | return nil, err 61 | } 62 | 63 | return &bir.Data, nil 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-bitcoin 2 | Go wrapper for bitcoin RPC 3 | 4 | ## RPC services 5 | Start by creating a connection to a bitcoin node 6 | ``` 7 | b, err := New("rcp host", rpc port, "rpc username", "rpc password", false) 8 | if err != nil { 9 | log.Fatal(err) 10 | } 11 | ``` 12 | 13 | Then make a call to bitcoin 14 | ``` 15 | res, err := b.GetBlockchainInfo() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | fmt.Printf("%#v\n", res) 20 | ``` 21 | 22 | Available calls are: 23 | ``` 24 | GetConnectionCount() 25 | GetBlockchainInfo() 26 | GetNetworkInfo() 27 | GetNetTotals() 28 | GetMiningInfo() 29 | Uptime() 30 | GetMempoolInfo() 31 | GetRawMempool(details bool) 32 | GetChainTxStats(blockcount int) 33 | ValidateAddress(address string) 34 | GetHelp() 35 | GetBestBlockHash() 36 | GetBlockHash(blockHeight int) 37 | SendRawTransaction(hex string) 38 | GetBlock(blockHash string) 39 | GetBlockOverview(blockHash string) 40 | GetBlockHex(blockHash string) 41 | GetRawTransaction(txID string) 42 | GetRawTransactionHex(txID string) 43 | GetBlockTemplate(includeSegwit bool) 44 | GetMiningCandidate() 45 | SubmitBlock(hexData string) 46 | SubmitMiningSolution(candidateID string, nonce uint32, 47 | coinbase string, time uint32, version uint32) 48 | GetDifficulty() 49 | DecodeRawTransaction(txHex string) 50 | GetTxOut(txHex string, vout int, includeMempool bool) 51 | ListUnspent(addresses []string) 52 | ``` 53 | 54 | ## ZMQ 55 | It is also possible to subscribe to a bitcoin node and be notified about new transactions and new blocks via the node's ZMQ interface. 56 | 57 | First, create a ZMQ instance: 58 | ``` 59 | zmq := bitcoin.NewZMQ("localhost", 28332) 60 | ``` 61 | 62 | Then create a buffered or unbuffered channel of strings and a goroutine to consume the channel: 63 | ``` 64 | ch := make(chan string) 65 | 66 | go func() { 67 | for c := range ch { 68 | log.Println(c) 69 | } 70 | }() 71 | ``` 72 | 73 | Finally, subscribe to "hashblock" or "hashtx" topics passing in your channel: 74 | ``` 75 | err := zmq.Subscribe("hashblock", ch) 76 | if err != nil { 77 | log.Fatalln(err) 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /End2End_test.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/libsv/go-bk/bec" 8 | "github.com/libsv/go-bt/v2" 9 | "github.com/libsv/go-bt/v2/bscript" 10 | "github.com/libsv/go-bt/v2/unlocker" 11 | ) 12 | 13 | func TestSendRawTransactions(t *testing.T) { 14 | // First we need a private key 15 | priv, err := bec.NewPrivateKey(bec.S256()) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | address, err := bscript.NewAddressFromPublicKey(priv.PubKey(), false) // false means "not mainnet" 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | lockingScript, err := bscript.NewP2PKHFromAddress(address.AddressString) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | txid, err := b.SendToAddress(address.AddressString, 1.0) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | fundingTxHex, err := b.GetRawTransactionHex(txid) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | fundingTx, err := bt.NewTxFromString(*fundingTxHex) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | var vout int = -1 51 | 52 | // Find the output that we can spend... 53 | for i, out := range fundingTx.Outputs { 54 | if out.LockingScript.String() == lockingScript.String() { 55 | vout = i 56 | break 57 | } 58 | } 59 | 60 | if vout == -1 { 61 | t.Fatal("Did not find a UTXO") 62 | } 63 | 64 | tx := bt.NewTx() 65 | if err := tx.From(fundingTx.TxID(), uint32(vout), fundingTx.Outputs[vout].LockingScriptHexString(), fundingTx.Outputs[vout].Satoshis); err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | amount := fundingTx.Outputs[vout].Satoshis / 2 70 | 71 | if err := tx.PayTo(lockingScript, amount); err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | fq := bt.NewFeeQuote() 76 | 77 | if err := tx.ChangeToAddress(address.AddressString, fq); err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | if err := tx.FillAllInputs(context.Background(), &unlocker.Getter{PrivateKey: priv}); err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | _, err = b.SendRawTransaction(tx.String()) 86 | if err != nil { 87 | t.Log(err) 88 | } 89 | 90 | newTxid2, err := b.SendRawTransactionWithoutFeeCheckOrScriptCheck(tx.String()) 91 | if err != nil { 92 | t.Log(err) 93 | } 94 | 95 | t.Logf("%+v", newTxid2) 96 | } 97 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bitbucket.org/simon_ordish/cryptolib v1.0.48 h1:bz3DsRCQK1tPQ59d1KwqDCkS0wW2PU6VoLlgTK/jKZM= 2 | bitbucket.org/simon_ordish/cryptolib v1.0.48/go.mod h1:Zt5cEbIFQyX6LxtDy7rZ5Bo2irVWJPZ1gQXWCsc66ZE= 3 | github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173 h1:2yTIV9u7H0BhRDGXH5xrAwAz7XibWJtX2dNezMeNsUo= 4 | github.com/bitcoinsv/bsvd v0.0.0-20190609155523-4c29707f7173/go.mod h1:BZ1UcC9+tmcDEcdVXgpt13hMczwJxWzpAn68wNs7zRA= 5 | github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9 h1:hFI8rT84FCA0FFy3cFrkW5Nz4FyNKlIdCvEvvTNySKg= 6 | github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9/go.mod h1:p44KuNKUH5BC8uX4ONEODaHUR4+ibC8todEAOGQEJAM= 7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/go-zeromq/goczmq/v4 v4.2.2 h1:HAJN+i+3NW55ijMJJhk7oWxHKXgAuSBkoFfvr8bYj4U= 11 | github.com/go-zeromq/goczmq/v4 v4.2.2/go.mod h1:Sm/lxrfxP/Oxqs0tnHD6WAhwkWrx+S+1MRrKzcxoaYE= 12 | github.com/go-zeromq/zmq4 v0.13.0 h1:XUWXLyeRsPsv4KlKMXnv/cEm//Vew2RLuNmDFQnZQXU= 13 | github.com/go-zeromq/zmq4 v0.13.0/go.mod h1:TrFwdPHMSLG7Rhp8OVhQBkb4bSajfucWv8rwoEFIgSY= 14 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 15 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 16 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 17 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 18 | github.com/libsv/go-bk v0.1.6 h1:c9CiT5+64HRDbzxPl1v/oiFmbvWZTuUYqywCf+MBs/c= 19 | github.com/libsv/go-bk v0.1.6/go.mod h1:khJboDoH18FPUaZlzRFKzlVN84d4YfdmlDtdX4LAjQA= 20 | github.com/libsv/go-bt/v2 v2.2.5 h1:VoggBLMRW9NYoFujqe5bSYKqnw5y+fYfufgERSoubog= 21 | github.com/libsv/go-bt/v2 v2.2.5/go.mod h1:cV45+jDlPOLfhJLfpLmpQoWzrIvVth9Ao2ZO1f6CcqU= 22 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 23 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 24 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 25 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 26 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 29 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 30 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 31 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 32 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 33 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 34 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 36 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 37 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 38 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 39 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /zmq.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | "log" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/go-zeromq/zmq4" 14 | ) 15 | 16 | var allowedTopics = []string{ 17 | "hashblock", 18 | "hashblock2", 19 | "hashtx", 20 | "hashtx2", 21 | "rawblock", 22 | "rawblock2", 23 | "rawtx", 24 | "rawtx2", 25 | "discardedfrommempool", 26 | "removedfrommempoolblock", 27 | "invalidtx", 28 | } 29 | 30 | type subscriptionRequest struct { 31 | topic string 32 | ch chan []string 33 | } 34 | 35 | // ZMQ struct 36 | type ZMQ struct { 37 | address string 38 | socket zmq4.Socket 39 | connected bool 40 | err error 41 | subscriptions map[string][]chan []string 42 | addSubscription chan subscriptionRequest 43 | removeSubscription chan subscriptionRequest 44 | logger Logger 45 | } 46 | 47 | func NewZMQ(host string, port int, optionalLogger ...Logger) *ZMQ { 48 | ctx := context.Background() 49 | 50 | return NewZMQWithContext(ctx, host, port, optionalLogger...) 51 | } 52 | 53 | func NewZMQWithContext(ctx context.Context, host string, port int, optionalLogger ...Logger) *ZMQ { 54 | 55 | zmq := &ZMQ{ 56 | address: fmt.Sprintf("tcp://%s:%d", host, port), 57 | subscriptions: make(map[string][]chan []string), 58 | addSubscription: make(chan subscriptionRequest, 10), 59 | removeSubscription: make(chan subscriptionRequest, 10), 60 | logger: &DefaultLogger{}, 61 | } 62 | 63 | if len(optionalLogger) > 0 { 64 | zmq.logger = optionalLogger[0] 65 | } 66 | 67 | go zmq.start(ctx) 68 | 69 | return zmq 70 | } 71 | 72 | func (zmq *ZMQ) Subscribe(topic string, ch chan []string) error { 73 | if !contains(allowedTopics, topic) { 74 | return fmt.Errorf("topic must be %+v, received %q", allowedTopics, topic) 75 | } 76 | 77 | zmq.addSubscription <- subscriptionRequest{ 78 | topic: topic, 79 | ch: ch, 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (zmq *ZMQ) Unsubscribe(topic string, ch chan []string) error { 86 | if !contains(allowedTopics, topic) { 87 | return fmt.Errorf("topic must be %+v, received %q", allowedTopics, topic) 88 | } 89 | 90 | zmq.removeSubscription <- subscriptionRequest{ 91 | topic: topic, 92 | ch: ch, 93 | } 94 | 95 | return nil 96 | } 97 | 98 | func (zmq *ZMQ) start(ctx context.Context) { 99 | for { 100 | zmq.socket = zmq4.NewSub(ctx, zmq4.WithID(zmq4.SocketIdentity("sub"))) 101 | defer func() { 102 | if zmq.connected { 103 | zmq.socket.Close() 104 | zmq.connected = false 105 | } 106 | }() 107 | 108 | if err := zmq.socket.Dial(zmq.address); err != nil { 109 | zmq.err = err 110 | zmq.logger.Errorf("Could not dial ZMQ at %s: %v", zmq.address, err) 111 | zmq.logger.Infof("Attempting to re-establish ZMQ connection in 10 seconds...") 112 | time.Sleep(10 * time.Second) 113 | continue 114 | } 115 | 116 | zmq.logger.Infof("ZMQ: Connecting to %s", zmq.address) 117 | 118 | for topic := range zmq.subscriptions { 119 | if err := zmq.socket.SetOption(zmq4.OptionSubscribe, topic); err != nil { 120 | zmq.err = fmt.Errorf("%+v", err) 121 | return 122 | } 123 | zmq.logger.Infof("ZMQ: Subscribed to %s", topic) 124 | } 125 | 126 | OUT: 127 | for { 128 | select { 129 | case <-ctx.Done(): 130 | zmq.logger.Infof("ZMQ: Context done, exiting") 131 | return 132 | case req := <-zmq.addSubscription: 133 | if err := zmq.socket.SetOption(zmq4.OptionSubscribe, req.topic); err != nil { 134 | zmq.logger.Errorf("ZMQ: Failed to subscribe to %s", req.topic) 135 | } else { 136 | zmq.logger.Infof("ZMQ: Subscribed to %s", req.topic) 137 | } 138 | 139 | subscribers := zmq.subscriptions[req.topic] 140 | subscribers = append(subscribers, req.ch) 141 | 142 | zmq.subscriptions[req.topic] = subscribers 143 | 144 | case req := <-zmq.removeSubscription: 145 | subscribers := zmq.subscriptions[req.topic] 146 | for i, subscriber := range subscribers { 147 | if subscriber == req.ch { 148 | subscribers = append(subscribers[:i], subscribers[i+1:]...) 149 | zmq.logger.Infof("Removed subscription from %s topic", req.topic) 150 | break 151 | } 152 | } 153 | zmq.subscriptions[req.topic] = subscribers 154 | 155 | default: 156 | msg, err := zmq.socket.Recv() 157 | if err != nil { 158 | if errors.Is(err, context.Canceled) { 159 | return 160 | } 161 | zmq.logger.Errorf("zmq.socket.Recv() - %v\n", err) 162 | break OUT 163 | } else { 164 | if !zmq.connected { 165 | zmq.connected = true 166 | zmq.logger.Infof("ZMQ: Connection to %s observed\n", zmq.address) 167 | } 168 | 169 | subscribers := zmq.subscriptions[string(msg.Frames[0])] 170 | 171 | sequence := "N/A" 172 | 173 | if len(msg.Frames) > 2 && len(msg.Frames[2]) == 4 { 174 | s := binary.LittleEndian.Uint32(msg.Frames[2]) 175 | sequence = strconv.FormatInt(int64(s), 10) 176 | } 177 | 178 | for _, subscriber := range subscribers { 179 | subscriber <- []string{string(msg.Frames[0]), hex.EncodeToString(msg.Frames[1]), sequence} 180 | } 181 | } 182 | } 183 | } 184 | 185 | if zmq.connected { 186 | zmq.socket.Close() 187 | zmq.connected = false 188 | } 189 | log.Printf("Attempting to re-establish ZMQ connection in 10 seconds...") 190 | time.Sleep(10 * time.Second) 191 | 192 | } 193 | } 194 | 195 | func contains(s []string, e string) bool { 196 | for _, a := range s { 197 | if a == e { 198 | return true 199 | } 200 | } 201 | return false 202 | } 203 | -------------------------------------------------------------------------------- /scratch.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | 7 | "bitbucket.org/simon_ordish/cryptolib" 8 | ) 9 | 10 | type input struct { 11 | hash [32]byte // The previous utxo being spent. 12 | index uint32 // The previous utxo index being spent. 13 | unlockingScript []byte // A script-language script which satisfies the conditions placed in the outpoint’s pubkey script. Should only contain data pushes; see https://bitcoin.org/en/developer-reference#signature_script_modification_warning. 14 | sequence uint32 // Sequence number. Default for Bitcoin Core and almost all other programs is 0xffffffff. See https://bitcoin.org/en/glossary/sequence-number 15 | } 16 | 17 | func (i *input) toHex() []byte { 18 | var b []byte 19 | 20 | b = append(b, cryptolib.ReverseBytes(i.hash[:])...) 21 | b = append(b, cryptolib.GetLittleEndianBytes(i.index, 4)...) 22 | b = append(b, cryptolib.VarInt(uint64(len(i.unlockingScript)))...) 23 | b = append(b, i.unlockingScript...) 24 | b = append(b, cryptolib.GetLittleEndianBytes(i.sequence, 4)...) 25 | 26 | return b 27 | } 28 | 29 | func inputFromBytes(b []byte) (*input, int) { 30 | pos := 0 31 | 32 | var previousOutput [32]byte 33 | copy(previousOutput[:], cryptolib.ReverseBytes(b[pos:pos+32])) 34 | 35 | pos += 32 36 | 37 | index := binary.LittleEndian.Uint32(b[pos : pos+4]) 38 | pos += 4 39 | 40 | scriptLen, size := cryptolib.DecodeVarInt(b[pos:]) 41 | pos += size 42 | 43 | len := int(scriptLen) 44 | 45 | script := b[pos : pos+len] 46 | pos += len 47 | 48 | sequence := binary.LittleEndian.Uint32(b[pos : pos+4]) 49 | pos += 4 50 | 51 | return &input{ 52 | hash: previousOutput, 53 | index: index, 54 | unlockingScript: script, 55 | sequence: sequence, 56 | }, pos 57 | } 58 | 59 | type output struct { 60 | value uint64 // Number of satoshis to spend. May be zero; the sum of all outputs may not exceed the sum of satoshis previously spent to the outpoints provided in the input section. (Exception: coinbase transactions spend the block subsidy and collected transaction fees.) 61 | lockingScript []byte // Defines the conditions which must be satisfied to spend this output. 62 | } 63 | 64 | func (o *output) toHex() []byte { 65 | var b []byte 66 | 67 | value := make([]byte, 8) 68 | binary.LittleEndian.PutUint64(value, o.value) 69 | 70 | b = append(b, value...) 71 | b = append(b, cryptolib.VarInt(uint64(len(o.lockingScript)))...) 72 | b = append(b, o.lockingScript...) 73 | 74 | return b 75 | } 76 | 77 | func outputFromBytes(b []byte) (*output, int) { 78 | pos := 0 79 | 80 | value := binary.LittleEndian.Uint64(b[pos : pos+8]) 81 | pos += 8 82 | 83 | scriptLen, size := cryptolib.DecodeVarInt(b[pos:]) 84 | pos += size 85 | 86 | len := int(scriptLen) 87 | 88 | script := b[pos : pos+len] 89 | pos += len 90 | 91 | return &output{ 92 | value: value, 93 | lockingScript: script, 94 | }, pos 95 | 96 | } 97 | 98 | type transaction struct { 99 | Hash string 100 | Version int32 // Transaction version number (note, this is signed); currently version 1 or 2. Programs creating transactions using newer consensus rules may use higher version numbers. Version 2 means that BIP 68 applies. 101 | Inputs []input // Transaction inputs. 102 | Outputs []output // Transaction outputs. 103 | LockTime uint32 // A time (Unix epoch time) or block number. See https://bitcoin.org/en/transactions-guide#locktime_parsing_rules 104 | } 105 | 106 | // TransactionFromHex takes a hex string and constructs a Transaction object 107 | func TransactionFromHex(h string) (*transaction, int) { 108 | s, _ := hex.DecodeString(h) 109 | return TransactionFromBytes(s) 110 | } 111 | 112 | // TransactionFromBytes takes a slice of bytes and constructs a Transaction object 113 | func TransactionFromBytes(b []byte) (*transaction, int) { 114 | pos := 0 115 | 116 | // extract the version 117 | version := binary.LittleEndian.Uint32(b[0:4]) 118 | pos += 4 119 | 120 | // Get the number of inputs 121 | numberOfInputs, size := cryptolib.DecodeVarInt(b[pos:]) 122 | pos += size 123 | 124 | var inputs []input 125 | 126 | for i := uint64(0); i < numberOfInputs; i++ { 127 | input, size := inputFromBytes(b[pos:]) 128 | pos += size 129 | 130 | inputs = append(inputs, *input) 131 | } 132 | 133 | // Get the number of outputs 134 | numberOfOutputs, size := cryptolib.DecodeVarInt(b[pos:]) 135 | pos += size 136 | 137 | var outputs []output 138 | 139 | for i := uint64(0); i < numberOfOutputs; i++ { 140 | output, size := outputFromBytes(b[pos:]) 141 | pos += size 142 | 143 | outputs = append(outputs, *output) 144 | } 145 | 146 | locktime := binary.LittleEndian.Uint32(b[pos : pos+4]) 147 | pos += 4 148 | 149 | hash := cryptolib.Sha256d(b[0:pos]) 150 | 151 | return &transaction{ 152 | Hash: hex.EncodeToString(cryptolib.ReverseBytes(hash)), 153 | Version: int32(version), 154 | Inputs: inputs, 155 | Outputs: outputs, 156 | LockTime: locktime, 157 | }, pos 158 | } 159 | 160 | func (t *transaction) InputCount() int { 161 | return len(t.Inputs) 162 | } 163 | 164 | func (t *transaction) OutputCount() int { 165 | return len(t.Outputs) 166 | } 167 | 168 | func (t *transaction) ToHex() []byte { 169 | var b []byte 170 | 171 | b = append(b, cryptolib.GetLittleEndianBytes(uint32(t.Version), 4)...) 172 | b = append(b, cryptolib.VarInt(uint64(t.InputCount()))...) 173 | for _, input := range t.Inputs { 174 | b = append(b, input.toHex()...) 175 | } 176 | 177 | b = append(b, cryptolib.VarInt(uint64(t.OutputCount()))...) 178 | for _, output := range t.Outputs { 179 | b = append(b, output.toHex()...) 180 | } 181 | 182 | b = append(b, cryptolib.GetLittleEndianBytes(t.LockTime, 4)...) 183 | 184 | return b 185 | } 186 | -------------------------------------------------------------------------------- /rpcClient.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/http/httptrace" 12 | "net/http/httputil" 13 | "os" 14 | "time" 15 | ) 16 | 17 | const ( 18 | rpcClientTimeoutSecondsDefault = 120 19 | ) 20 | 21 | var ( 22 | ErrTimeout = errors.New("Timeout reading data from server") 23 | debugHttpDumpBody = os.Getenv("debug_http_dump_body") 24 | debugHttp = os.Getenv("debug_http") 25 | ) 26 | 27 | // A rpcClient represents a JSON RPC client (over HTTP(s)). 28 | type rpcClient struct { 29 | serverAddr string 30 | user string 31 | passwd string 32 | httpClient *http.Client 33 | logger Logger 34 | rpcClientTimeout time.Duration 35 | } 36 | 37 | // rpcRequest represent a RCP request 38 | type rpcRequest struct { 39 | Method string `json:"method"` 40 | Params interface{} `json:"params"` 41 | ID int64 `json:"id"` 42 | JSONRpc string `json:"jsonrpc"` 43 | } 44 | 45 | // rpcError represents a RCP error 46 | /*type rpcError struct { 47 | Code int16 `json:"code"` 48 | Message string `json:"message"` 49 | }*/ 50 | 51 | // rpcResponse represents a RCP response 52 | type rpcResponse struct { 53 | ID int64 `json:"id"` 54 | Result json.RawMessage `json:"result"` 55 | Err interface{} `json:"error"` 56 | } 57 | 58 | func (c *rpcClient) debug(data []byte, err error) { 59 | if err == nil { 60 | c.logger.Infof("%s\n\n", data) 61 | } else { 62 | c.logger.Errorf("ERROR: %s\n\n", err) 63 | } 64 | } 65 | 66 | func WithTimeoutDuration(d time.Duration) func(*rpcClient) { 67 | return func(p *rpcClient) { 68 | p.rpcClientTimeout = d 69 | } 70 | } 71 | 72 | func WithOptionalLogger(l Logger) func(*rpcClient) { 73 | return func(p *rpcClient) { 74 | p.logger = l 75 | } 76 | } 77 | 78 | type Option func(f *rpcClient) 79 | 80 | func newClient(host string, port int, user, passwd string, useSSL bool, opts ...Option) (c *rpcClient, err error) { 81 | if len(host) == 0 { 82 | err = errors.New("Bad call missing argument host") 83 | return 84 | } 85 | var serverAddr string 86 | var httpClient *http.Client 87 | if useSSL { 88 | serverAddr = "https://" 89 | t := &http.Transport{ 90 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 91 | } 92 | httpClient = &http.Client{Transport: t} 93 | } else { 94 | serverAddr = "http://" 95 | httpClient = &http.Client{} 96 | } 97 | c = &rpcClient{ 98 | serverAddr: fmt.Sprintf("%s%s:%d", serverAddr, host, port), 99 | user: user, 100 | passwd: passwd, 101 | httpClient: httpClient, 102 | logger: &DefaultLogger{}, 103 | rpcClientTimeout: rpcClientTimeoutSecondsDefault * time.Second, 104 | } 105 | 106 | // apply options to client 107 | for _, opt := range opts { 108 | opt(c) 109 | } 110 | 111 | return 112 | } 113 | 114 | // doTimeoutRequest process a HTTP request with timeout 115 | func (c *rpcClient) doTimeoutRequest(timer *time.Timer, req *http.Request) (*http.Response, error) { 116 | type result struct { 117 | resp *http.Response 118 | err error 119 | } 120 | done := make(chan result, 1) 121 | go func() { 122 | if debugHttp == "true" { 123 | c.debug(httputil.DumpRequestOut(req, debugHttpDumpBody == "true")) 124 | } 125 | resp, err := c.httpClient.Do(req) 126 | done <- result{resp, err} 127 | }() 128 | // Wait for the read or the timeout 129 | select { 130 | case r := <-done: 131 | if debugHttp == "true" { 132 | c.debug(httputil.DumpResponse(r.resp, debugHttpDumpBody == "true")) 133 | } 134 | return r.resp, r.err 135 | case <-timer.C: 136 | return nil, ErrTimeout 137 | } 138 | } 139 | 140 | // call prepare & exec the request 141 | func (c *rpcClient) call(method string, params interface{}) (rpcResponse, error) { 142 | connectTimer := time.NewTimer(c.rpcClientTimeout) 143 | rpcR := rpcRequest{method, params, time.Now().UnixNano(), "1.0"} 144 | payloadBuffer := &bytes.Buffer{} 145 | jsonEncoder := json.NewEncoder(payloadBuffer) 146 | 147 | err := jsonEncoder.Encode(rpcR) 148 | if err != nil { 149 | return rpcResponse{}, fmt.Errorf("failed to encode rpc request: %w", err) 150 | } 151 | 152 | req, err := http.NewRequest("POST", c.serverAddr, payloadBuffer) 153 | if err != nil { 154 | return rpcResponse{}, fmt.Errorf("failed to create new http request: %w", err) 155 | } 156 | 157 | if os.Getenv("HTTP_TRACE") == "TRUE" { 158 | trace := &httptrace.ClientTrace{ 159 | DNSDone: func(dnsInfo httptrace.DNSDoneInfo) { 160 | c.logger.Debugf("HTTP_TRACE - DNS: %+v\n", dnsInfo) 161 | }, 162 | GotConn: func(connInfo httptrace.GotConnInfo) { 163 | c.logger.Debugf("HTTP_TRACE - Conn: %+v\n", connInfo) 164 | }} 165 | ctxTrace := httptrace.WithClientTrace(req.Context(), trace) 166 | 167 | req = req.WithContext(ctxTrace) 168 | } 169 | 170 | req.Header.Add("Content-Type", "application/json;charset=utf-8") 171 | req.Header.Add("Accept", "application/json") 172 | 173 | // Auth ? 174 | if len(c.user) > 0 || len(c.passwd) > 0 { 175 | req.SetBasicAuth(c.user, c.passwd) 176 | } 177 | 178 | resp, err := c.doTimeoutRequest(connectTimer, req) 179 | if err != nil { 180 | return rpcResponse{}, fmt.Errorf("failed to do request: %w", err) 181 | } 182 | defer resp.Body.Close() 183 | 184 | data, err := io.ReadAll(resp.Body) 185 | if err != nil { 186 | return rpcResponse{}, fmt.Errorf("failed to read response: %w", err) 187 | } 188 | 189 | var rr rpcResponse 190 | 191 | if resp.StatusCode != 200 { 192 | _ = json.Unmarshal(data, &rr) 193 | v, ok := rr.Err.(map[string]interface{}) 194 | if ok { 195 | err = errors.New(v["message"].(string)) 196 | } else { 197 | err = errors.New("HTTP error: " + resp.Status) 198 | } 199 | 200 | return rr, fmt.Errorf("unexpected response code %d: %w", resp.StatusCode, err) 201 | } 202 | 203 | err = json.Unmarshal(data, &rr) 204 | if err != nil { 205 | return rr, fmt.Errorf("failed to unmarshal response: %w", err) 206 | } 207 | 208 | return rr, nil 209 | } 210 | 211 | // call prepare & exec the request 212 | func (c *rpcClient) read(method string, params interface{}) (io.ReadCloser, error) { 213 | connectTimer := time.NewTimer(c.rpcClientTimeout) 214 | rpcR := rpcRequest{method, params, time.Now().UnixNano(), "1.0"} 215 | payloadBuffer := &bytes.Buffer{} 216 | jsonEncoder := json.NewEncoder(payloadBuffer) 217 | 218 | err := jsonEncoder.Encode(rpcR) 219 | if err != nil { 220 | return nil, fmt.Errorf("failed to encode rpc request: %w", err) 221 | } 222 | 223 | req, err := http.NewRequest("POST", c.serverAddr, payloadBuffer) 224 | if err != nil { 225 | return nil, fmt.Errorf("failed to create new http request: %w", err) 226 | } 227 | 228 | req.Header.Add("Content-Type", "application/json;charset=utf-8") 229 | req.Header.Add("Accept", "application/json") 230 | 231 | // Auth ? 232 | if len(c.user) > 0 || len(c.passwd) > 0 { 233 | req.SetBasicAuth(c.user, c.passwd) 234 | } 235 | 236 | resp, err := c.doTimeoutRequest(connectTimer, req) 237 | if err != nil { 238 | return nil, fmt.Errorf("failed to do request: %w", err) 239 | } 240 | 241 | if resp.StatusCode != 200 { 242 | defer resp.Body.Close() 243 | 244 | var rr rpcResponse 245 | data, err := io.ReadAll(resp.Body) 246 | 247 | if err != nil { 248 | return nil, fmt.Errorf("failed to read response: %w", err) 249 | } 250 | 251 | _ = json.Unmarshal(data, &rr) 252 | v, ok := rr.Err.(map[string]interface{}) 253 | if ok { 254 | err = errors.New(v["message"].(string)) 255 | } else { 256 | err = errors.New("HTTP error: " + resp.Status) 257 | } 258 | 259 | return nil, fmt.Errorf("unexpected response code %d: %w", resp.StatusCode, err) 260 | } 261 | 262 | return resp.Body, nil 263 | } 264 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /RPC_test.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | // Force a new commit 4 | 5 | import ( 6 | "encoding/hex" 7 | "fmt" 8 | "io" 9 | "net" 10 | "strings" 11 | "testing" 12 | "time" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestGetBlockChainInfo(t *testing.T) { 19 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | res, err := b.GetBlockchainInfo() 25 | if err != nil { 26 | t.Error(err) 27 | t.FailNow() 28 | } 29 | t.Logf("%#v", res) 30 | } 31 | 32 | func TestGetConnectionCount(t *testing.T) { 33 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | count, err := b.GetConnectionCount() 39 | if err != nil { 40 | t.Error(err) 41 | t.FailNow() 42 | } 43 | t.Logf("%d", count) 44 | } 45 | 46 | func TestGetNetworkInfo(t *testing.T) { 47 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | res, err := b.GetNetworkInfo() 53 | if err != nil { 54 | t.Error(err) 55 | t.FailNow() 56 | } 57 | t.Logf("%#v", res) 58 | t.Logf("Actual IP was %s", b.IPAddress) 59 | } 60 | 61 | func TestGetNetTotals(t *testing.T) { 62 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | res, err := b.GetNetTotals() 68 | if err != nil { 69 | t.Error(err) 70 | t.FailNow() 71 | } 72 | t.Logf("%#v", res) 73 | } 74 | func TestMiningInfo(t *testing.T) { 75 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | res, err := b.GetMiningInfo() 81 | if err != nil { 82 | t.Error(err) 83 | t.FailNow() 84 | } 85 | t.Logf("%#v", res) 86 | } 87 | 88 | func TestUptime(t *testing.T) { 89 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | 94 | count, err := b.Uptime() 95 | if err != nil { 96 | t.Error(err) 97 | t.FailNow() 98 | } 99 | t.Logf("%d", count) 100 | } 101 | 102 | func TestGetPeerInfo(t *testing.T) { 103 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | res, err := b.GetPeerInfo() 109 | if err != nil { 110 | t.Error(err) 111 | t.FailNow() 112 | } 113 | t.Logf("%#v", res) 114 | } 115 | 116 | func TestGetRawMempoolWithDetails(t *testing.T) { 117 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | 122 | res, err := b.GetRawMempool(true) 123 | if err != nil { 124 | t.Error(err) 125 | t.FailNow() 126 | } 127 | t.Logf("%s", string(res)) 128 | } 129 | 130 | func TestGetRawMempoolNoDetails(t *testing.T) { 131 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | res, err := b.GetRawMempool(false) 137 | if err != nil { 138 | t.Error(err) 139 | t.FailNow() 140 | } 141 | t.Logf("%s", string(res)) 142 | } 143 | 144 | func TestGetMempoolInfo(t *testing.T) { 145 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | res, err := b.GetMempoolInfo() 151 | if err != nil { 152 | t.Error(err) 153 | t.FailNow() 154 | } 155 | t.Logf("%#v", res) 156 | } 157 | 158 | func TestGetChainTxStats(t *testing.T) { 159 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | stats, err := b.GetChainTxStats(0) 165 | if err != nil { 166 | t.Error(err) 167 | t.FailNow() 168 | } 169 | t.Logf("%#v", stats) 170 | } 171 | 172 | func TestValidateAddress(t *testing.T) { 173 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | 178 | stats, err := b.ValidateAddress("13q47QSaXBHMZVFHFENtTpfd7rtaWTe3v1") 179 | if err != nil { 180 | t.Error(err) 181 | t.FailNow() 182 | } 183 | t.Logf("%#v", stats) 184 | } 185 | 186 | func TestHelp(t *testing.T) { 187 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | 192 | res, err := b.GetHelp() 193 | if err != nil { 194 | t.Error(err) 195 | t.FailNow() 196 | } 197 | t.Logf("%s", res) 198 | } 199 | 200 | func TestGetBestBlockHash(t *testing.T) { 201 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 202 | if err != nil { 203 | t.Fatal(err) 204 | } 205 | 206 | res, err := b.GetBestBlockHash() 207 | if err != nil { 208 | t.Error(err) 209 | t.FailNow() 210 | } 211 | t.Logf("%s", res) 212 | } 213 | 214 | func TestGetBlockHash(t *testing.T) { 215 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 216 | if err != nil { 217 | t.Fatal(err) 218 | } 219 | 220 | res, err := b.GetBlockHash(1) 221 | if err != nil { 222 | t.Error(err) 223 | t.FailNow() 224 | } 225 | t.Logf("%s", res) 226 | } 227 | 228 | func TestSendRawTransaction2(t *testing.T) { 229 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 230 | if err != nil { 231 | t.Fatal(err) 232 | } 233 | 234 | res, err := b.SendRawTransaction("0100000002b212995825c72d1abf5505a3017694f564dda29908fe5d45ab45b6a3e1748ca5010000006a473044022030d77fbaf3ec092a5038e7fc0235bb2126ca5099dccac5383d58cea01a8d329302207581678aa91743abe31c4cf54e16b0c58e05631d20ccde33be242d96cb57ff1141210373b675c91c95391b6d7977de12bf081bae193470aa0199efc1f847d799497b67ffffffff428947c3e5fb13faf1e71ea86b209b88c3b2e1cd77b326b696fc6e9fb3d0ced6000000006b483045022100801a316894dc40d1e335a14d25b6a7b5b72691b0842878105942d86303a09e1502204d737aecf5d1972e880e329363681bb7c97334e262cf4a20293fee3d8ca87d7041210373b675c91c95391b6d7977de12bf081bae193470aa0199efc1f847d799497b67ffffffff030a1a0000000000001976a914dc9ad4971a54b52308fa3c958df73eac52fb552f88acc6160000000000001976a914a933500e7326f81a974d4212aa16ae29f92e257188ac00000000000000003d6a3b53656e642076696120612057656368617420626f74206d616465206279206161726f6e363720687474703a2f2f6269742e6c792f333331536d754300000000") 235 | assert.Equal(t, "unexpected response code 500: Missing inputs", err.Error()) 236 | t.Logf("%s", res) 237 | } 238 | 239 | func TestSendRawTransaction(t *testing.T) { 240 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 241 | if err != nil { 242 | t.Fatal(err) 243 | } 244 | 245 | res, err := b.SendRawTransaction("0100000002b212995825c72d1abf5505a3017694f564dda29908fe5d45ab45b6a3e1748ca5010000006a473044022030d77fbaf3ec092a5038e7fc0235bb2126ca5099dccac5383d58cea01a8d329302207581678aa91743abe31c4cf54e16b0c58e05631d20ccde33be242d96cb57ff1141210373b675c91c95391b6d7977de12bf081bae193470aa0199efc1f847d799497b67ffffffff428947c3e5fb13faf1e71ea86b209b88c3b2e1cd77b326b696fc6e9fb3d0ced6000000006b483045022100801a316894dc40d1e335a14d25b6a7b5b72691b0842878105942d86303a09e1502204d737aecf5d1972e880e329363681bb7c97334e262cf4a20293fee3d8ca87d7041210373b675c91c95391b6d7977de12bf081bae193470aa0199efc1f847d799497b67ffffffff030a1a0000000000001976a914dc9ad4971a54b52308fa3c958df73eac52fb552f88acc6160000000000001976a914a933500e7326f81a974d4212aa16ae29f92e257188ac00000000000000003d6a3b53656e642076696120612057656368617420626f74206d616465206279206161726f6e363720687474703a2f2f6269742e6c792f333331536d754300000000") 246 | assert.Equal(t, "unexpected response code 500: Missing inputs", err.Error()) 247 | t.Logf("%s", res) 248 | } 249 | 250 | func TestSendRawTransactionWithoutFeeCheckOrScriptCheck(t *testing.T) { 251 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | 256 | res, err := b.SendRawTransactionWithoutFeeCheckOrScriptCheck("02000000012cf2bccc35c87b1f6ba2de5e7d175bc0be054230b557efc12636cc092faca6760000000049483045022100f323d358de7e06a43bf1cb272072c283593eaccb90455a6dd5947ae50a0b157b0220212d2208f211ba4be88141cc8c73cf6f9f4116c78e1bb8ffe2d27cb4326ba06f41ffffffff01f0ca052a010000001976a9142a5acfb9a647a03a758afaa5c359284d4b95c0be88ac00000000") 257 | assert.Equal(t, "Transaction invalid: missing-inputs", err.Error()) 258 | t.Logf("%s", res) 259 | } 260 | 261 | func TestGetBlockOverview(t *testing.T) { 262 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 263 | if err != nil { 264 | t.Fatal(err) 265 | } 266 | 267 | blk, err := b.GetBlockByHeight(102) 268 | require.NoError(t, err) 269 | 270 | res, err := b.GetBlockOverview(blk.Hash) 271 | if err != nil { 272 | t.Error(err) 273 | t.FailNow() 274 | } 275 | t.Logf("%#v", res) 276 | } 277 | 278 | func TestGetBlock(t *testing.T) { 279 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 280 | if err != nil { 281 | t.Fatal(err) 282 | } 283 | 284 | blk, err := b.GetBlockByHeight(102) 285 | require.NoError(t, err) 286 | 287 | res, err := b.GetBlock(blk.Hash) 288 | if err != nil { 289 | t.Error(err) 290 | t.FailNow() 291 | } 292 | t.Logf("%#v", res) 293 | } 294 | 295 | func TestGetBlockByHeight(t *testing.T) { 296 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 297 | if err != nil { 298 | t.Fatal(err) 299 | } 300 | 301 | res, err := b.GetBlockByHeight(102) 302 | if err != nil { 303 | t.Error(err) 304 | t.FailNow() 305 | } 306 | t.Logf("%#v", res) 307 | } 308 | 309 | func TestGetBlockStatsByHeight(t *testing.T) { 310 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | 315 | res, err := b.GetBlockStatsByHeight(102) 316 | if err != nil { 317 | t.Error(err) 318 | t.FailNow() 319 | } 320 | t.Logf("%#v", res) 321 | } 322 | 323 | func TestGetBlockStats(t *testing.T) { 324 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 325 | if err != nil { 326 | t.Fatal(err) 327 | } 328 | 329 | blk, err := b.GetBlockByHeight(102) 330 | require.NoError(t, err) 331 | 332 | res, err := b.GetBlockStats(blk.Hash) 333 | if err != nil { 334 | t.Error(err) 335 | t.FailNow() 336 | } 337 | t.Logf("%#v", res) 338 | } 339 | 340 | func TestGetGenesisBlock(t *testing.T) { 341 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 342 | if err != nil { 343 | t.Fatal(err) 344 | } 345 | 346 | blk, err := b.GetBlockByHeight(0) 347 | require.NoError(t, err) 348 | 349 | res, err := b.GetBlock(blk.Hash) 350 | if err != nil { 351 | t.Error(err) 352 | t.FailNow() 353 | } 354 | t.Logf("%#v", res) 355 | } 356 | 357 | func TestGetBlockHex(t *testing.T) { 358 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | 363 | blk, err := b.GetBlockByHeight(102) 364 | require.NoError(t, err) 365 | 366 | res, err := b.GetBlockHex(blk.Hash) 367 | if err != nil { 368 | t.Error(err) 369 | t.FailNow() 370 | } 371 | t.Logf("%s", *res) 372 | } 373 | 374 | func TestGetBlockHeaderHex(t *testing.T) { 375 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 376 | if err != nil { 377 | t.Fatal(err) 378 | } 379 | 380 | blk, err := b.GetBlockByHeight(102) 381 | require.NoError(t, err) 382 | 383 | res, err := b.GetBlockHeaderHex(blk.Hash) 384 | if err != nil { 385 | t.Error(err) 386 | t.FailNow() 387 | } 388 | t.Logf("%s", *res) 389 | } 390 | 391 | func TestGetBlockHeader(t *testing.T) { 392 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 393 | if err != nil { 394 | t.Fatal(err) 395 | } 396 | 397 | blk, err := b.GetBlockByHeight(102) 398 | require.NoError(t, err) 399 | 400 | res, err := b.GetBlockHeader(blk.Hash) 401 | if err != nil { 402 | t.Error(err) 403 | t.FailNow() 404 | } 405 | t.Logf("%+v", res) 406 | } 407 | 408 | func TestGetBlockHeaderAndCoinbase(t *testing.T) { 409 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 410 | if err != nil { 411 | t.Fatal(err) 412 | } 413 | 414 | blk, err := b.GetBlockByHeight(102) 415 | require.NoError(t, err) 416 | 417 | res, err := b.GetBlockHeaderAndCoinbase(blk.Hash) 418 | if err != nil { 419 | t.Error(err) 420 | t.FailNow() 421 | } 422 | t.Logf("%+v", res) 423 | } 424 | 425 | func TestGetRawTransaction(t *testing.T) { 426 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 427 | if err != nil { 428 | t.Fatal(err) 429 | } 430 | 431 | blk, err := b.GetBlockByHeight(102) 432 | require.NoError(t, err) 433 | 434 | res, err := b.GetRawTransaction(blk.Tx[0]) 435 | if err != nil { 436 | t.Error(err) 437 | t.FailNow() 438 | } 439 | t.Logf("%#v", res) 440 | } 441 | 442 | func TestGetRawTransactionHex(t *testing.T) { 443 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 444 | if err != nil { 445 | t.Fatal(err) 446 | } 447 | 448 | blk, err := b.GetBlockByHeight(102) 449 | require.NoError(t, err) 450 | 451 | tx, err := b.GetRawTransactionHex(blk.Tx[0]) 452 | if err != nil { 453 | t.Error(err) 454 | t.FailNow() 455 | } 456 | t.Logf("%#v", *tx) 457 | } 458 | func TestGetDifficulty(t *testing.T) { 459 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 460 | if err != nil { 461 | t.Fatal(err) 462 | } 463 | 464 | diff, err := b.GetDifficulty() 465 | if err != nil { 466 | t.Error(err) 467 | t.FailNow() 468 | } 469 | t.Logf("%f", diff) 470 | } 471 | 472 | // func TestGetBlockTemplate(t *testing.T) { 473 | // b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 474 | // if err != nil { 475 | // t.Fatal(err) 476 | // } 477 | 478 | // // Passing true as an argument will set the segwit rule. This is necessary for 479 | // // BTC and ignored for BCH and BSV. 480 | // template, err := b.GetBlockTemplate(true) 481 | // if err != nil { 482 | // t.Error(err) 483 | // t.FailNow() 484 | // } 485 | // t.Logf("%#v", template) 486 | // } 487 | 488 | // func TestGetMiningCandidate(t *testing.T) { 489 | // b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 490 | // if err != nil { 491 | // t.Fatal(err) 492 | // } 493 | 494 | // template, err := b.GetMiningCandidate() 495 | // if err != nil { 496 | // t.Error(err) 497 | // t.FailNow() 498 | // } 499 | // t.Logf("%#v", template) 500 | // } 501 | 502 | func TestGetSettings(t *testing.T) { 503 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 504 | if err != nil { 505 | t.Fatal(err) 506 | } 507 | 508 | template, err := b.GetSettings() 509 | if err != nil { 510 | t.Error(err) 511 | t.FailNow() 512 | } 513 | t.Logf("%#v", template) 514 | } 515 | 516 | func TestGetTxOut(t *testing.T) { 517 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 518 | if err != nil { 519 | t.Fatal(err) 520 | } 521 | 522 | blk, err := b.GetBlockByHeight(102) 523 | require.NoError(t, err) 524 | 525 | res, err := b.GetTxOut(blk.Tx[0], 0, true) 526 | if err != nil { 527 | t.Error(err) 528 | t.FailNow() 529 | } 530 | 531 | t.Logf("%#v", res) 532 | } 533 | 534 | func TestSubmitBlock(t *testing.T) { 535 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 536 | if err != nil { 537 | t.Fatal(err) 538 | } 539 | 540 | template, err := b.SubmitBlock("00000020c803e566bf5af601f19ecb1878f5a6bc2188841c46530c0200000000000000007493d05e5f3ef63975027947bba8c2e08aae8b74e267d9e11e6a4d10e0c0ca3770223e5dc53c081801991f080101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5f031c0d09046f223e5d2f706f6f6c696e2e636f6d2ffabe6d6ddd5fe0e090d85e6b39d0a6a19bd9b2ff3e3a05113d8ec3bd4a23e39e9c70aec00100000000000000736899c85c811ff6d3eade8b24b8f71213c3bb6d4d003843004000000000ffffffff02807c814a000000001976a91431f2bece272b5d346aa56d09101dd7306d9a307588ac0000000000000000266a24b9e11b6d4ee6b780e5c5ec8a57bc28030aaf0b43632f319648f8bca6abf1ebe3739511f0502c1759") 541 | assert.Equal(t, "******* BLOCK SUBMIT FAILED with error: and result: \"inconclusive\"\n", err.Error()) 542 | t.Logf("%#v", template) 543 | } 544 | 545 | func TestSubmitMiningSolution(t *testing.T) { 546 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 547 | if err != nil { 548 | t.Fatal(err) 549 | } 550 | 551 | var ( 552 | miningCandidateID = "ABC" 553 | nonce uint32 554 | coinbase string 555 | time uint32 556 | version uint32 557 | ) 558 | 559 | template, err := b.SubmitMiningSolution(miningCandidateID, nonce, coinbase, time, version) 560 | assert.Equal(t, "******* BLOCK SUBMIT FAILED with error: unexpected response code 500: bad lexical cast: source type value could not be interpreted as target and result: null\n", err.Error()) 561 | t.Logf("%#v", template) 562 | } 563 | 564 | func TestDecodeRawTransactionHex(t *testing.T) { 565 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 566 | if err != nil { 567 | t.Fatal(err) 568 | } 569 | 570 | tx, err := b.DecodeRawTransaction("01000000017235e81ebaccd9fd4d14e3381825c6ac37b56096320e1c54fe00d45e124eca0a0000000000ffffffff01f03a0000000000001976a91424575db8999bc36cd89999de7172b64df2a8893588ac00000000") 571 | if err != nil { 572 | t.Error(err) 573 | t.FailNow() 574 | } 575 | t.Logf("%#v", tx) 576 | } 577 | 578 | func TestListUnspent(t *testing.T) { 579 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 580 | if err != nil { 581 | t.Fatal(err) 582 | } 583 | 584 | tx, err := b.ListUnspent([]string{"n38vndTAZKFzc3BtPAJ4mecp44UwAZVski"}) 585 | if err != nil { 586 | t.Error(err) 587 | t.FailNow() 588 | } 589 | 590 | for _, utxo := range tx { 591 | t.Logf("%#v", utxo) 592 | } 593 | } 594 | 595 | func TestSendToAddress(t *testing.T) { 596 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 597 | if err != nil { 598 | t.Fatal(err) 599 | } 600 | 601 | tx, err := b.SendToAddress("n38vndTAZKFzc3BtPAJ4mecp44UwAZVski", 0.01) 602 | if err != nil { 603 | t.Error(err) 604 | t.FailNow() 605 | } 606 | 607 | t.Log(tx) 608 | } 609 | 610 | func TestGetRawBlock(t *testing.T) { 611 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 612 | if err != nil { 613 | t.Fatal(err) 614 | } 615 | blk, err := b.GetBlockByHeight(102) 616 | require.NoError(t, err) 617 | 618 | blockBytes, err := b.GetRawBlock(blk.Hash) 619 | if err != nil { 620 | t.Error(err) 621 | t.FailNow() 622 | } 623 | 624 | t.Log(hex.EncodeToString(blockBytes)) 625 | 626 | block, err := b.GetBlock(blk.Hash) 627 | if err != nil { 628 | t.Error(err) 629 | t.FailNow() 630 | } 631 | 632 | t.Logf("%+v", block) 633 | } 634 | 635 | func TestGetRawBlockRest(t *testing.T) { 636 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 637 | if err != nil { 638 | t.Fatal(err) 639 | } 640 | 641 | blk, err := b.GetBlockByHeight(102) 642 | require.NoError(t, err) 643 | 644 | r, err := b.GetRawBlockRest(blk.Hash) 645 | if err != nil { 646 | t.Error(err) 647 | t.FailNow() 648 | } 649 | 650 | defer r.Close() 651 | 652 | data, err := io.ReadAll(r) 653 | if err != nil { 654 | t.Error(err) 655 | t.FailNow() 656 | } 657 | 658 | t.Log(hex.EncodeToString(data)) 659 | } 660 | 661 | func TestSendToNewAddress(t *testing.T) { 662 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 663 | require.NoError(t, err) 664 | 665 | addr, err := b.GetNewAddress() 666 | require.NoError(t, err) 667 | 668 | t.Log(addr) 669 | 670 | privKey, err := b.DumpPrivKey(addr) 671 | require.NoError(t, err) 672 | 673 | t.Log(privKey) 674 | 675 | err = b.SetAccount(addr, "test-account") 676 | require.NoError(t, err) 677 | 678 | _, err = b.Generate(101) 679 | require.NoError(t, err) 680 | 681 | txID, err := b.SendToAddress(addr, 0.01) 682 | require.NoError(t, err) 683 | 684 | utxos, err := b.ListUnspent([]string{addr}) 685 | require.NoError(t, err) 686 | 687 | txIDfound := false 688 | for _, utxo := range utxos { 689 | if utxo.TXID == txID { 690 | txIDfound = true 691 | break 692 | } 693 | } 694 | 695 | require.True(t, txIDfound) 696 | } 697 | 698 | func TestGetRawTransactionRest(t *testing.T) { 699 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 700 | if err != nil { 701 | t.Fatal(err) 702 | } 703 | 704 | blk, err := b.GetBlockByHeight(102) 705 | require.NoError(t, err) 706 | 707 | r, err := b.GetRawTransactionRest(blk.Tx[0]) 708 | if err != nil { 709 | t.Error(err) 710 | t.FailNow() 711 | } 712 | 713 | defer r.Close() 714 | 715 | data, err := io.ReadAll(r) 716 | if err != nil { 717 | t.Error(err) 718 | t.FailNow() 719 | } 720 | 721 | t.Log(hex.EncodeToString(data)) 722 | } 723 | 724 | func TestSendTime(t *testing.T) { 725 | keyfunc1 := func(method string, params []interface{}) string { 726 | var s strings.Builder 727 | 728 | s.WriteString(method) 729 | s.WriteRune('-') 730 | 731 | s.WriteString(params[0].(string)) 732 | s.WriteRune('|') 733 | 734 | if params[1].(bool) { 735 | s.WriteRune('T') 736 | } else { 737 | s.WriteRune('F') 738 | } 739 | 740 | if params[2].(bool) { 741 | s.WriteRune('T') 742 | } else { 743 | s.WriteRune('F') 744 | } 745 | 746 | return s.String() 747 | } 748 | 749 | keyfunc2 := func(method string, params []interface{}) string { 750 | return fmt.Sprintf("%s-%v", method, params) 751 | } 752 | 753 | keyfunc3 := func(method string, params []interface{}) string { 754 | var s strings.Builder 755 | 756 | fmt.Fprintf(&s, "%s-%s|", method, params[0].(string)) 757 | 758 | if params[1].(bool) { 759 | s.WriteRune('T') 760 | } else { 761 | s.WriteRune('F') 762 | } 763 | 764 | if params[2].(bool) { 765 | s.WriteRune('T') 766 | } else { 767 | s.WriteRune('F') 768 | } 769 | 770 | return s.String() 771 | } 772 | 773 | method := "sendrawtransaction" 774 | hex := "12345677" 775 | params := []interface{}{hex, false, true} 776 | times := 1_000_000 777 | 778 | start := time.Now() 779 | 780 | for i := 0; i < times; i++ { 781 | _ = keyfunc1(method, params) 782 | } 783 | 784 | t.Logf("1. %s - Took %s\n", keyfunc1(method, params), time.Since(start)) 785 | 786 | start = time.Now() 787 | 788 | for i := 0; i < times; i++ { 789 | _ = keyfunc2(method, params) 790 | } 791 | 792 | t.Logf("2. %s - Took %s\n", keyfunc2(method, params), time.Since(start)) 793 | 794 | start = time.Now() 795 | 796 | for i := 0; i < times; i++ { 797 | _ = keyfunc3(method, params) 798 | } 799 | 800 | t.Logf("2. %s - Took %s\n", keyfunc3(method, params), time.Since(start)) 801 | } 802 | 803 | func TestGetTime(t *testing.T) { 804 | keyfunc1 := func(method string, params []interface{}) string { 805 | var b strings.Builder 806 | 807 | b.WriteString(method) 808 | b.WriteRune('-') 809 | b.WriteString(params[0].(string)) 810 | b.WriteRune('|') 811 | b.WriteByte(byte(params[1].(int))) 812 | 813 | return b.String() 814 | } 815 | 816 | keyfunc2 := func(method string, params []interface{}) string { 817 | var b strings.Builder 818 | 819 | b.WriteString(method) 820 | b.WriteRune('-') 821 | b.WriteString(params[0].(string)) 822 | b.WriteRune('|') 823 | if params[1].(int) == 0 { 824 | b.WriteRune('0') 825 | } else { 826 | b.WriteRune('1') 827 | } 828 | 829 | return b.String() 830 | } 831 | 832 | method := "getrawtransaction" 833 | hex := "12345677" 834 | params := []interface{}{hex, 1} 835 | times := 1_000_000 836 | 837 | start := time.Now() 838 | 839 | for i := 0; i < times; i++ { 840 | _ = keyfunc1(method, params) 841 | } 842 | 843 | t.Logf("1. %s - Took %s\n", keyfunc1(method, params), time.Since(start)) 844 | 845 | start = time.Now() 846 | 847 | for i := 0; i < times; i++ { 848 | _ = keyfunc2(method, params) 849 | } 850 | 851 | t.Logf("2. %s - Took %s\n", keyfunc2(method, params), time.Since(start)) 852 | 853 | } 854 | 855 | func TestDNS(t *testing.T) { 856 | ips, err := net.LookupIP("google.com") 857 | if err != nil || len(ips) == 0 { 858 | t.Error(err) 859 | t.FailNow() 860 | } 861 | 862 | for _, i := range ips { 863 | t.Logf("%t, %#v", i.To4() != nil, i.String()) 864 | } 865 | } 866 | 867 | func TestErrTimeout(t *testing.T) { 868 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false, WithTimeoutDuration(1*time.Millisecond)) 869 | if err != nil { 870 | t.Fatal(err) 871 | } 872 | 873 | _, err = b.ListUnspent([]string{"n38vndTAZKFzc3BtPAJ4mecp44UwAZVski"}) 874 | require.ErrorIs(t, err, ErrTimeout) 875 | 876 | } 877 | 878 | func TestGetMerkleProof(t *testing.T) { 879 | b, err := New("localhost", 18332, "bitcoin", "bitcoin", false) 880 | if err != nil { 881 | t.Fatal(err) 882 | } 883 | 884 | blk, err := b.GetBlockByHeight(102) 885 | require.NoError(t, err) 886 | 887 | res, err := b.GetMerkleProof(blk.Hash, blk.Tx[0]) 888 | if err != nil { 889 | t.Error(err) 890 | t.FailNow() 891 | } 892 | t.Logf("%#v", res) 893 | } 894 | -------------------------------------------------------------------------------- /JSON.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | type gbtParams struct { 4 | Mode string `json:"mode,omitempty"` 5 | Capabilities []string `json:"capabilities,omitempty"` 6 | Rules []string `json:"rules,omitempty"` 7 | } 8 | 9 | // BlockchainInfo comment 10 | type BlockchainInfo struct { 11 | Chain string `json:"chain"` 12 | Blocks int32 `json:"blocks"` 13 | Headers int32 `json:"headers"` 14 | BestBlockHash string `json:"bestblockhash"` 15 | Difficulty float64 `json:"difficulty"` 16 | MedianTime int64 `json:"mediantime"` 17 | VerificationProgress float64 `json:"verificationprogress,omitempty"` 18 | Pruned bool `json:"pruned"` 19 | PruneHeight int32 `json:"pruneheight,omitempty"` 20 | ChainWork string `json:"chainwork,omitempty"` 21 | } 22 | 23 | // GetInfo comment 24 | type GetInfo struct { 25 | Version int32 `json:"version"` 26 | ProtocolVersion int32 `json:"protocolversion"` 27 | WalletVersion int32 `json:"walletversion"` 28 | Balance float64 `json:"balance"` 29 | InitComplete bool `json:"initcomplete"` 30 | Blocks int32 `json:"blocks"` 31 | TimeOffset int64 `json:"timeoffset"` 32 | Connections int32 `json:"connections"` 33 | Proxy string `json:"proxy"` 34 | Difficulty float64 `json:"difficulty"` 35 | TestNet bool `json:"testnet"` 36 | STN bool `json:"stn"` 37 | KeyPoolOldest int64 `json:"keypoololdest"` 38 | KeyPoolSize int32 `json:"keypoolsize"` 39 | PayTXFee float64 `json:"paytxfee"` 40 | RelayFee float64 `json:"relayfee"` 41 | Errors string `json:"errors"` 42 | MaxBlockSize int64 `json:"maxblocksize"` 43 | MaxMinedBlockSize int64 `json:"maxminedblocksize"` 44 | MaxStackMemoryUsagePolicy uint64 `json:"maxstackmemoryusagepolicy"` 45 | MaxStackMemoryUsageConsensus uint64 `json:"maxstackmemoryusageconsensus"` 46 | } 47 | 48 | type Settings struct { 49 | ExcessiveBlockSize int `json:"excessiveblocksize"` 50 | BlockMaxSize int `json:"blockmaxsize"` 51 | MaxTxSizePolicy int `json:"maxtxsizepolicy"` 52 | MaxOrphanTxSize int `json:"maxorphantxsize"` 53 | DataCarrierSize int64 `json:"datacarriersize"` 54 | MaxScriptSizePolicy int `json:"maxscriptsizepolicy"` 55 | MaxOpsPerScriptPolicy int64 `json:"maxopsperscriptpolicy"` 56 | MaxScriptNumLengthPolicy int `json:"maxscriptnumlengthpolicy"` 57 | MaxPubKeysPerMultisigPolicy int64 `json:"maxpubkeyspermultisigpolicy"` 58 | MaxTxSigopsCountsPolicy int64 `json:"maxtxsigopscountspolicy"` 59 | MaxStackMemoryUsagePolicy int `json:"maxstackmemoryusagepolicy"` 60 | MaxStackMemoryUsageConsensus int `json:"maxstackmemoryusageconsensus"` 61 | LimitAncestorCount int `json:"limitancestorcount"` 62 | LimitCPFPGroupMembersCount int `json:"limitcpfpgroupmemberscount"` 63 | MaxMempool int `json:"maxmempool"` 64 | MaxMempoolSizedisk int `json:"maxmempoolsizedisk"` 65 | MempoolMaxPercentCPFP int `json:"mempoolmaxpercentcpfp"` 66 | AcceptNonStdOutputs bool `json:"acceptnonstdoutputs"` 67 | DataCarrier bool `json:"datacarrier"` 68 | MinMiningTxFee float64 `json:"minminingtxfee"` 69 | MaxStdTxValidationDuration int `json:"maxstdtxvalidationduration"` 70 | MaxNonStdTxValidationDuration int `json:"maxnonstdtxvalidationduration"` 71 | MaxTxChainValidationBudget int `json:"maxtxchainvalidationbudget"` 72 | ValidationClockCpu bool `json:"validationclockcpu"` 73 | MinConsolidationFactor int `json:"minconsolidationfactor"` 74 | MaxConsolidationInputScriptSize int `json:"maxconsolidationinputscriptsize"` 75 | MinConfConsolidationInput int `json:"minconfconsolidationinput"` 76 | MinConsolidationInputMaturity int `json:"minconsolidationinputmaturity"` 77 | AcceptNonStdConsolidationInput bool `json:"acceptnonstdconsolidationinput"` 78 | } 79 | 80 | type Tip struct { 81 | Height uint64 `json:"height"` 82 | Hash string `json:"hash"` 83 | BranchLen uint32 `json:"branchlen"` 84 | Status string `json:"status"` 85 | } 86 | 87 | // ChainTips comment 88 | type ChainTips []Tip 89 | 90 | // Network comment 91 | type Network struct { 92 | Name string `json:"name"` 93 | Limited bool `json:"limited"` 94 | Reachable bool `json:"reachable"` 95 | Proxy string `json:"proxy"` 96 | ProxyRandmomizeCredentials bool `json:"proxy_randomize_credentials"` 97 | } 98 | 99 | // LocalAddress comment 100 | type LocalAddress struct { 101 | Address string `json:"address"` 102 | Port int `json:"port"` 103 | Score int `json:"score"` 104 | } 105 | 106 | // NetworkInfo comment 107 | type NetworkInfo struct { 108 | Version int `json:"version"` 109 | SubVersion string `json:"subversion"` 110 | ProtocolVersion int `json:"protocolversion"` 111 | LocalServices string `json:"localservices"` 112 | LocalRelay bool `json:"localrelay"` 113 | TimeOffset int `json:"timeoffset"` 114 | TXPropagationFreq int `json:"txnpropagationfreq"` 115 | TXPropagationLen int `json:"txnpropagationqlen"` 116 | NetworkActive bool `json:"networkactive"` 117 | Connections int `json:"connections"` 118 | AddressCount int `json:"addresscount"` 119 | Networks []Network `json:"networks"` 120 | RelayFee float64 `json:"relayfee"` 121 | MinConsolidationFactor int `json:"minconsolidationfactor"` 122 | MinConsolidationInputMaturity int `json:"minconsolidationinputmaturity"` 123 | MaxConsolidationInputScriptSize int `json:"maxconsolidationinputscriptsize"` 124 | AcceptNonStdConsolidationInput bool `json:"acceptnonstdconsolidationinput"` 125 | ExcessUTXOCharge float64 `json:"excessutxocharge"` 126 | LocalAddresses []LocalAddress `json:"localaddresses"` 127 | Warnings string `json:"warnings"` 128 | } 129 | 130 | // NetTotals comment 131 | type NetTotals struct { 132 | TotalBytesRecv int `json:"totalbytesrecv"` 133 | TotalBytesSent int `json:"totalbytessent"` 134 | TimeMillis int `json:"timemillis"` 135 | UploadTarget struct { 136 | TimeFrame int `json:"timeframe"` 137 | Target int `json:"target"` 138 | TargetReached bool `json:"target_reached"` 139 | ServeHistoricalBlocks bool `json:"serve_historical_blocks"` 140 | BytesLeftInCycle int `json:"bytes_left_in_cycle"` 141 | TimeLeftInCycle int `json:"time_left_in_cycle"` 142 | } `json:"uploadtarget"` 143 | } 144 | 145 | // MiningInfo comment 146 | type MiningInfo struct { 147 | Blocks int `json:"blocks"` 148 | CurrentBlockSize int `json:"currentblocksize"` 149 | CurrentBlockTX int `json:"currentblocktx"` 150 | Difficulty float64 `json:"difficulty"` 151 | BlocksPriorityPercent int `json:"blockprioritypercentage"` 152 | Errors string `json:"errors"` 153 | NetworkHashPS float64 `json:"networkhashps"` 154 | PooledTX int `json:"pooledtx"` 155 | Chain string `json:"chain"` 156 | } 157 | 158 | // BytesData struct 159 | type BytesData struct { 160 | Addr int `json:"addr"` 161 | BlockTXN int `json:"blocktxn"` 162 | CmpctBlock int `json:"cmpctblock"` 163 | FeeFilter int `json:"feefilter"` 164 | GetAddr int `json:"getaddr"` 165 | GetData int `json:"getdata"` 166 | GetHeaders int `json:"getheaders"` 167 | Headers int `json:"headers"` 168 | Inv int `json:"inv"` 169 | NotFound int `json:"notfound"` 170 | Ping int `json:"ping"` 171 | Pong int `json:"pong"` 172 | Reject int `json:"reject"` 173 | SendCmpct int `json:"sendcmpct"` 174 | SendHeaders int `json:"sendheaders"` 175 | TX int `json:"tx"` 176 | VerAck int `json:"verack"` 177 | Version int `json:"version"` 178 | } 179 | 180 | // Peer struct 181 | type Peer struct { 182 | ID int `json:"id"` 183 | Addr string `json:"addr"` 184 | AddrLocal string `json:"addrlocal"` 185 | Services string `json:"services"` 186 | RelayTXes bool `json:"relaytxes"` 187 | LastSend int `json:"lastsend"` 188 | LastRecv int `json:"lastrecv"` 189 | BytesSent int `json:"bytessent"` 190 | BytesRecv int `json:"bytesrecv"` 191 | ConnTime int `json:"conntime"` 192 | TimeOffset int `json:"timeoffset"` 193 | PingTime float64 `json:"pingtime"` 194 | MinPing float64 `json:"minping"` 195 | Version int `json:"version"` 196 | Subver string `json:"subver"` 197 | Inbound bool `json:"inbound"` 198 | AddNode bool `json:"addnode"` 199 | StartingHeight int `json:"startingheight"` 200 | TXNInvSize int `json:"txninvsize"` 201 | Banscore int `json:"banscore"` 202 | SyncedHeaders int `json:"synced_headers"` 203 | SyncedBlocks int `json:"synced_blocks"` 204 | // "inflight": [], 205 | WhiteListed bool `json:"whitelisted"` 206 | BytesSendPerMsg BytesData `json:"bytessent_per_msg"` 207 | BytesRecvPerMsg BytesData `json:"bytesrecv_per_msg"` 208 | } 209 | 210 | // PeerInfo comment 211 | type PeerInfo []Peer 212 | 213 | // RawMemPool comment 214 | type RawMemPool []string 215 | 216 | // MempoolInfo comment 217 | type MempoolInfo struct { 218 | Size int `json:"size"` // Current tx count 219 | JournalSize int `json:"journalsize"` // Current tx count within the journal 220 | NonFinalSize int `json:"nonfinalsize"` // Current non-final tx count 221 | Bytes int `json:"bytes"` // Transaction size 222 | Usage int `json:"usage"` // Total memory usage for the mempool 223 | UsageDisk int `json:"usagedisk"` // Total disk usage for storing mempool transactions 224 | UsageCpfp int `json:"usagecpfp"` // Total memory usage for the low paying transactions 225 | NonFinalUsage int `json:"nonfinalusage"` // Total memory usage for the non-final mempool 226 | MaxMemPool int `json:"maxmempool"` // Maximum memory usage for the mempool 227 | MaxMempoolSizeDisk int `json:"maxmempoolsizedisk"` // Maximum disk usage for storing mempool transactions 228 | MaxMempoolSizeCpfp int `json:"maxmempoolsizecpfp"` // Maximum memory usage for the low paying transactions 229 | MemPoolMinFree float64 `json:"mempoolminfee"` // Minimum fee (in BSV/kB) for tx to be accepted 230 | } 231 | 232 | type MempoolEntry struct { 233 | Size int `json:"size"` 234 | Fee float64 `json:"fee"` 235 | ModifiedFee float64 `json:"modifiedfee"` 236 | Time int `json:"time"` 237 | Height int `json:"height"` 238 | Depends []string `json:"depends"` 239 | } 240 | 241 | // ChainTXStats struct 242 | type ChainTXStats struct { 243 | Time int `json:"time"` 244 | TXCount int `json:"txcount"` 245 | WindowBlockCount int `json:"window_block_count"` 246 | WindowTXCount int `json:"window_tx_count"` 247 | WindowInterval int `json:"window_interval"` 248 | TXRate float64 `json:"txrate"` 249 | } 250 | 251 | // Address comment 252 | type Address struct { 253 | IsValid bool `json:"isvalid"` 254 | Address string `json:"address"` 255 | ScriptPubKey string `json:"scriptPubKey"` 256 | IsMine bool `json:"ismine"` 257 | IsWatchOnly bool `json:"iswatchonly"` 258 | IsScript bool `json:"isscript"` 259 | } 260 | 261 | // Transaction comment 262 | type Transaction struct { 263 | TXID string `json:"txid"` 264 | Hash string `json:"hash"` 265 | Data string `json:"data"` 266 | } 267 | 268 | // BlockTemplate comment 269 | type BlockTemplate struct { 270 | Version uint32 `json:"version"` 271 | PreviousBlockHash string `json:"previousblockhash"` 272 | Target string `json:"target"` 273 | Transactions []Transaction `json:"transactions"` 274 | Bits string `json:"bits"` 275 | CurTime uint64 `json:"curtime"` 276 | CoinbaseValue uint64 `json:"coinbasevalue"` 277 | Height uint32 `json:"height"` 278 | MinTime uint64 `json:"mintime"` 279 | NonceRange string `json:"noncerange"` 280 | DefaultWitnessCommitment string `json:"default_witness_commitment"` 281 | SizeLimit uint64 `json:"sizelimit"` 282 | WeightLimit uint64 `json:"weightlimit"` 283 | SigOpLimit int64 `json:"sigoplimit"` 284 | VBRequired int64 `json:"vbrequired"` 285 | // extra mining candidate fields 286 | IsMiningCandidate bool `json:"-"` 287 | MiningCandidateID string `json:"-"` 288 | MiningCandidate *MiningCandidate `json:"-"` 289 | MerkleBranches []string `json:"-"` 290 | } 291 | 292 | // MiningCandidate comment 293 | type MiningCandidate struct { 294 | ID string `json:"id"` 295 | PreviousHash string `json:"prevhash"` 296 | CoinbaseValue uint64 `json:"coinbaseValue"` 297 | Version uint32 `json:"version"` 298 | Bits string `json:"nBits"` 299 | CurTime uint64 `json:"time"` 300 | Height uint32 `json:"height"` 301 | NumTx uint32 `json:"num_tx"` 302 | SizeWithoutCoinbase uint64 `json:"sizeWithoutCoinbase"` 303 | MerkleProof []string `json:"merkleProof"` 304 | } 305 | 306 | type submitMiningSolutionParams struct { 307 | ID string `json:"id"` 308 | Nonce uint32 `json:"nonce"` 309 | Coinbase string `json:"coinbase"` 310 | Time uint32 `json:"time"` 311 | Version uint32 `json:"version"` 312 | } 313 | 314 | // Block struct 315 | type Block struct { 316 | Hash string `json:"hash"` 317 | Confirmations int64 `json:"confirmations"` 318 | Size uint64 `json:"size"` 319 | Height uint64 `json:"height"` 320 | Version int64 `json:"version"` 321 | VersionHex string `json:"versionHex"` 322 | MerkleRoot string `json:"merkleroot"` 323 | TxCount uint64 `json:"txcount"` 324 | NTx uint64 `json:"nTx"` 325 | NumTx uint64 `json:"num_tx"` 326 | Tx []string `json:"tx"` 327 | Time uint64 `json:"time"` 328 | MedianTime uint64 `json:"mediantime"` 329 | Nonce uint64 `json:"nonce"` 330 | Bits string `json:"bits"` 331 | Difficulty float64 `json:"difficulty"` 332 | Chainwork string `json:"chainwork"` 333 | PreviousBlockHash string `json:"previousblockhash"` 334 | NextBlockHash string `json:"nextblockhash"` 335 | // extra properties 336 | CoinbaseTx *RawTransaction `json:"coinbaseTx"` 337 | TotalFees float64 `json:"totalFees"` 338 | Miner string `json:"miner"` 339 | Pagination *BlockPage `json:"pages"` 340 | } 341 | 342 | // Block2 struct 343 | type Block2 struct { 344 | Hash string `json:"hash"` 345 | Size int `json:"size"` 346 | Height int `json:"height"` 347 | Version uint32 `json:"version"` 348 | VersionHex string `json:"versionHex"` 349 | MerkleRoot string `json:"merkleroot"` 350 | TxCount uint64 `json:"txcount"` 351 | NTx uint64 `json:"nTx"` 352 | NumTx uint64 `json:"num_tx"` 353 | Tx []string `json:"tx"` 354 | Time uint32 `json:"time"` 355 | MedianTime uint32 `json:"mediantime"` 356 | Nonce uint32 `json:"nonce"` 357 | Bits string `json:"bits"` 358 | Difficulty float64 `json:"difficulty"` 359 | Chainwork string `json:"chainwork"` 360 | PreviousBlockHash string `json:"previousblockhash"` 361 | NextBlockHash string `json:"nextblockhash"` 362 | BlockSubsidy uint64 `json:"blockSubsidy"` 363 | BlockReward uint64 `json:"blockReward"` 364 | USDPrice float64 `json:"usdPrice"` 365 | Miner string `json:"miner"` 366 | } 367 | 368 | // BlockOverview struct 369 | type BlockOverview struct { 370 | Hash string `json:"hash"` 371 | Confirmations int64 `json:"confirmations"` 372 | Size uint64 `json:"size"` 373 | Height uint64 `json:"height"` 374 | Version int64 `json:"version"` 375 | VersionHex string `json:"versionHex"` 376 | MerkleRoot string `json:"merkleroot"` 377 | // TxCount uint64 `json:"txcount"` 378 | Time uint64 `json:"time"` 379 | MedianTime uint64 `json:"mediantime"` 380 | Nonce uint64 `json:"nonce"` 381 | Bits string `json:"bits"` 382 | Difficulty float64 `json:"difficulty"` 383 | Chainwork string `json:"chainwork"` 384 | PreviousBlockHash string `json:"previousblockhash"` 385 | NextBlockHash string `json:"nextblockhash"` 386 | } 387 | 388 | // BlockHeader comment 389 | type BlockHeader struct { 390 | Hash string `json:"hash"` 391 | Confirmations int64 `json:"confirmations"` 392 | Size uint64 `json:"size"` 393 | Height uint64 `json:"height"` 394 | Version uint64 `json:"version"` 395 | VersionHex string `json:"versionHex"` 396 | MerkleRoot string `json:"merkleroot"` 397 | Time uint64 `json:"time"` 398 | MedianTime uint64 `json:"mediantime"` 399 | Nonce uint64 `json:"nonce"` 400 | Bits string `json:"bits"` 401 | Difficulty float64 `json:"difficulty"` 402 | Chainwork string `json:"chainwork"` 403 | PreviousBlockHash string `json:"previousblockhash"` 404 | NextBlockHash string `json:"nextblockhash"` 405 | NTx uint64 `json:"nTx"` 406 | TxCount uint64 `json:"num_tx"` 407 | } 408 | 409 | // BlockHeaderAndCoinbase comment 410 | type BlockHeaderAndCoinbase struct { 411 | Hash string `json:"hash"` 412 | Confirmations int64 `json:"confirmations"` 413 | Size uint64 `json:"size"` 414 | Height uint64 `json:"height"` 415 | Version uint64 `json:"version"` 416 | VersionHex string `json:"versionHex"` 417 | MerkleRoot string `json:"merkleroot"` 418 | NumTx uint64 `json:"num_tx"` 419 | Time uint64 `json:"time"` 420 | MedianTime uint64 `json:"mediantime"` 421 | Nonce uint64 `json:"nonce"` 422 | Bits string `json:"bits"` 423 | Difficulty float64 `json:"difficulty"` 424 | Chainwork string `json:"chainwork"` 425 | PreviousBlockHash string `json:"previousblockhash"` 426 | NextBlockHash string `json:"nextblockhash"` 427 | Tx []RawTransaction `json:"tx"` 428 | } 429 | 430 | type BlockStats struct { 431 | AvgFee float64 `json:"avgfee"` 432 | AvgFeeRate float64 `json:"avgfeerate"` 433 | AvgTxSize int `json:"avgtxsize"` 434 | BlockHash string `json:"blockhash"` 435 | Height int `json:"height"` 436 | Ins int `json:"ins"` 437 | MaxFee float64 `json:"maxfee"` 438 | MaxFeeRate float64 `json:"maxfeerate"` 439 | MaxTxSize int `json:"maxtxsize"` 440 | MedianFee float64 `json:"medianfee"` 441 | MedianFeeRate float64 `json:"medianfeerate"` 442 | MedianTime int `json:"mediantime"` 443 | MedianTxSize int `json:"mediantxsize"` 444 | MinFee float64 `json:"minfee"` 445 | MinFeeRate float64 `json:"minfeerate"` 446 | MinTxSize int `json:"mintxsize"` 447 | Outs int `json:"outs"` 448 | Subsidy float64 `json:"subsidy"` 449 | Time int `json:"time"` 450 | TotalOut float64 `json:"total_out"` 451 | TotalSize int `json:"total_size"` 452 | TotalFee float64 `json:"totalfee"` 453 | Txs int `json:"txs"` 454 | UtxoIncrease int `json:"utxo_increase"` 455 | UtxoSizeInc int `json:"utxo_size_inc"` 456 | } 457 | 458 | // BlockPage to store links 459 | type BlockPage struct { 460 | URI []string `json:"uri"` 461 | Size uint64 `json:"size"` 462 | } 463 | 464 | // BlockTxid comment 465 | type BlockTxid struct { 466 | BlockHash string `json:"blockhash"` 467 | Tx []string `json:"tx"` 468 | StartIndex uint64 `json:"startIndex"` 469 | EndIndex uint64 `json:"endIndex"` 470 | Count uint64 `json:"count"` 471 | } 472 | 473 | // RawTransaction comment 474 | type RawTransaction struct { 475 | Hex string `json:"hex,omitempty"` 476 | TxID string `json:"txid"` 477 | Hash string `json:"hash"` 478 | Version int32 `json:"version"` 479 | Size uint32 `json:"size"` 480 | LockTime uint32 `json:"locktime"` 481 | Vin []*Vin `json:"vin"` 482 | Vout []*Vout `json:"vout"` 483 | BlockHash string `json:"blockhash,omitempty"` 484 | Confirmations uint32 `json:"confirmations,omitempty"` 485 | Time int64 `json:"time,omitempty"` 486 | Blocktime int64 `json:"blocktime,omitempty"` 487 | BlockHeight uint64 `json:"blockheight,omitempty"` 488 | } 489 | 490 | // Vout represent an OUT value 491 | type Vout struct { 492 | Value float64 `json:"value"` 493 | N int `json:"n"` 494 | ScriptPubKey ScriptPubKey `json:"scriptPubKey"` 495 | } 496 | 497 | // Vin represent an IN value 498 | type Vin struct { 499 | Coinbase string `json:"coinbase"` 500 | Txid string `json:"txid"` 501 | Vout uint64 `json:"vout"` 502 | ScriptSig ScriptSig `json:"scriptSig"` 503 | Sequence uint32 `json:"sequence"` 504 | } 505 | 506 | // OpReturn comment 507 | type OpReturn struct { 508 | Type string `json:"type"` 509 | Action string `json:"action"` 510 | Text string `json:"text"` 511 | Parts []string `json:"parts"` 512 | } 513 | 514 | // Tag 515 | type Tag struct { 516 | Type string `json:"type"` 517 | Action string `json:"action"` 518 | } 519 | 520 | // ScriptPubKey Comment 521 | type ScriptPubKey struct { 522 | ASM string `json:"asm"` 523 | Hex string `json:"hex"` 524 | ReqSigs int64 `json:"reqSigs,omitempty"` 525 | Type string `json:"type"` 526 | Addresses []string `json:"addresses,omitempty"` 527 | OpReturn *OpReturn `json:"opReturn,omitempty"` 528 | Tag *Tag `json:"tag,omitempty"` 529 | IsTruncated bool `json:"isTruncated"` 530 | } 531 | 532 | // A ScriptSig represents a scriptsig 533 | type ScriptSig struct { 534 | ASM string `json:"asm"` 535 | Hex string `json:"hex"` 536 | } 537 | 538 | // UnspentTransaction type 539 | type UnspentTransaction struct { 540 | TXID string `json:"txid"` 541 | Vout uint32 `json:"vout"` 542 | Address string `json:"address"` 543 | ScriptPubKey string `json:"scriptPubKey"` 544 | Amount float64 `json:"amount"` 545 | Satoshis uint64 `json:"satoshis"` 546 | Confirmations uint32 `json:"confirmations"` 547 | } 548 | 549 | type TXOut struct { 550 | BestBlock string `json:"bestblock"` 551 | Confirmations int `json:"confirmations"` 552 | Value float64 `json:"value"` 553 | ScriptPubKey ScriptPubKey `json:"scriptPubKey"` 554 | Coinbase bool `json:"coinbase"` 555 | } 556 | 557 | // SignRawTransactionResponse struct 558 | type SignRawTransactionResponse struct { 559 | Hex string `json:"hex"` 560 | Complete bool `json:"complete"` 561 | } 562 | 563 | // Error comment 564 | type Error struct { 565 | Code float64 566 | Message string 567 | } 568 | 569 | // getmerkleproof2 response 570 | type MerkleProof struct { 571 | Index int `json:"index"` 572 | TxOrId string `json:"txOrId"` 573 | TargetType string `json:"targetType,omitempty"` 574 | Target string `json:"target"` 575 | Nodes []string `json:"nodes"` 576 | } 577 | -------------------------------------------------------------------------------- /RPC.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "bitbucket.org/simon_ordish/cryptolib" 17 | cache "github.com/patrickmn/go-cache" 18 | "golang.org/x/sync/singleflight" 19 | ) 20 | 21 | // A Bitcoind represents a Bitcoind client 22 | type Bitcoind struct { 23 | client *rpcClient 24 | Storage *cache.Cache 25 | group singleflight.Group 26 | IPAddress string 27 | } 28 | 29 | func NewFromURL(url *url.URL, useSSL bool, opts ...Option) (*Bitcoind, error) { 30 | port, err := strconv.Atoi(url.Port()) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | password, _ := url.User.Password() 36 | 37 | return New(url.Hostname(), port, url.User.Username(), password, useSSL, opts...) 38 | } 39 | 40 | // New return a new bitcoind 41 | func New(host string, port int, user, passwd string, useSSL bool, opts ...Option) (*Bitcoind, error) { 42 | ips, err := net.LookupIP(host) 43 | if err != nil || len(ips) == 0 { 44 | return nil, fmt.Errorf("Could not resolve %q: %v", host, err) 45 | } 46 | 47 | var ip string 48 | 49 | for _, i := range ips { 50 | if i.To4() != nil { 51 | ip = i.String() 52 | break 53 | } 54 | } 55 | 56 | rpcClient, err := newClient(ip, port, user, passwd, useSSL, opts...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | defaultExpiration := 5 * time.Second 62 | cleanupInterval := 10 * time.Second 63 | 64 | return &Bitcoind{ 65 | client: rpcClient, 66 | Storage: cache.New(defaultExpiration, cleanupInterval), 67 | group: singleflight.Group{}, 68 | IPAddress: ip, 69 | }, nil 70 | } 71 | 72 | func (b *Bitcoind) call(method string, params []interface{}) (rpcResponse, error) { 73 | keyfunc := func(method string, params []interface{}) string { 74 | return fmt.Sprintf("%s|%v", method, params) 75 | } 76 | 77 | return b.callWithKeyFunc(method, params, keyfunc) 78 | } 79 | 80 | func (b *Bitcoind) callWithKeyFunc(method string, params []interface{}, keyfunc func(string, []interface{}) string) (rpcResponse, error) { 81 | key := keyfunc(method, params) 82 | 83 | // Check cache 84 | value, found := b.Storage.Get(key) 85 | if found { 86 | // fmt.Printf("CACHED: ") 87 | return value.(rpcResponse), nil 88 | } 89 | 90 | // Combine memoized function with a cache store 91 | value, err, _ := b.group.Do(key, func() (interface{}, error) { 92 | data, innerErr := b.client.call(method, params) 93 | 94 | if innerErr == nil { 95 | b.Storage.Set(key, data, cache.DefaultExpiration) 96 | } 97 | 98 | return data, innerErr 99 | }) 100 | return value.(rpcResponse), err 101 | } 102 | 103 | func (b *Bitcoind) read(method string, params []interface{}) (io.ReadCloser, error) { 104 | return b.client.read(method, params) 105 | } 106 | 107 | // GetConnectionCount returns the number of connections to other nodes. 108 | func (b *Bitcoind) GetConnectionCount() (count uint64, err error) { 109 | r, err := b.call("getconnectioncount", nil) 110 | if err != nil { 111 | return 0, err 112 | } 113 | count, err = strconv.ParseUint(string(r.Result), 10, 64) 114 | return 115 | } 116 | 117 | // GetBlockchainInfo returns the number of connections to other nodes. 118 | func (b *Bitcoind) GetBlockchainInfo() (info BlockchainInfo, err error) { 119 | r, err := b.call("getblockchaininfo", nil) 120 | if err != nil { 121 | return 122 | } 123 | 124 | if r.Err != nil { 125 | rr := r.Err.(map[string]interface{}) 126 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 127 | return 128 | } 129 | 130 | err = json.Unmarshal(r.Result, &info) 131 | return 132 | } 133 | 134 | // GetInfo returns the number of connections to other nodes. 135 | func (b *Bitcoind) GetInfo() (info GetInfo, err error) { 136 | r, err := b.call("getinfo", nil) 137 | if err != nil { 138 | return 139 | } 140 | 141 | if r.Err != nil { 142 | rr := r.Err.(map[string]interface{}) 143 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 144 | return 145 | } 146 | 147 | err = json.Unmarshal(r.Result, &info) 148 | return 149 | } 150 | 151 | // GetInfo returns the number of connections to other nodes. 152 | func (b *Bitcoind) GetSettings() (settings Settings, err error) { 153 | r, err := b.call("getsettings", nil) 154 | if err != nil { 155 | return 156 | } 157 | 158 | if r.Err != nil { 159 | rr := r.Err.(map[string]interface{}) 160 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 161 | return 162 | } 163 | 164 | err = json.Unmarshal(r.Result, &settings) 165 | return 166 | } 167 | 168 | // GetNetworkInfo returns the number of connections to other nodes. 169 | func (b *Bitcoind) GetNetworkInfo() (info NetworkInfo, err error) { 170 | r, err := b.call("getnetworkinfo", nil) 171 | if err != nil { 172 | return 173 | } 174 | 175 | if r.Err != nil { 176 | rr := r.Err.(map[string]interface{}) 177 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 178 | return 179 | } 180 | 181 | err = json.Unmarshal(r.Result, &info) 182 | return 183 | } 184 | 185 | // GetNetTotals returns the number of connections to other nodes. 186 | func (b *Bitcoind) GetNetTotals() (totals NetTotals, err error) { 187 | r, err := b.call("getnettotals", nil) 188 | if err != nil { 189 | return 190 | } 191 | 192 | if r.Err != nil { 193 | rr := r.Err.(map[string]interface{}) 194 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 195 | return 196 | } 197 | 198 | err = json.Unmarshal(r.Result, &totals) 199 | return 200 | } 201 | 202 | // GetMiningInfo comment 203 | func (b *Bitcoind) GetMiningInfo() (info MiningInfo, err error) { 204 | r, err := b.call("getmininginfo", nil) 205 | if err != nil { 206 | return 207 | } 208 | 209 | if r.Err != nil { 210 | rr := r.Err.(map[string]interface{}) 211 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 212 | return 213 | } 214 | 215 | err = json.Unmarshal(r.Result, &info) 216 | return 217 | } 218 | 219 | // Uptime returns the number of connections to other nodes. 220 | func (b *Bitcoind) Uptime() (uptime uint64, err error) { 221 | r, err := b.call("uptime", nil) 222 | if err != nil { 223 | return 0, err 224 | } 225 | uptime, err = strconv.ParseUint(string(r.Result), 10, 64) 226 | return 227 | } 228 | 229 | // GetPeerInfo returns the number of connections to other nodes. 230 | func (b *Bitcoind) GetPeerInfo() (info PeerInfo, err error) { 231 | r, err := b.call("getpeerinfo", nil) 232 | if err != nil { 233 | return 234 | } 235 | 236 | if r.Err != nil { 237 | rr := r.Err.(map[string]interface{}) 238 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 239 | return 240 | } 241 | 242 | err = json.Unmarshal(r.Result, &info) 243 | return 244 | } 245 | 246 | // GetChainTips return information about all known tips in the block tree, including the main chain as well as orphaned branches. 247 | // Possible values for status: 248 | // 1. "invalid" This branch contains at least one invalid block 249 | // 2. "headers-only" Not all blocks for this branch are available, but the headers are valid 250 | // 3. "valid-headers" All blocks are available for this branch, but they were never fully validated 251 | // 4. "valid-fork" This branch is not part of the active chain, but is fully validated 252 | // 5. "active" This is the tip of the active main chain, which is certainly valid 253 | func (b *Bitcoind) GetChainTips() (tips ChainTips, err error) { 254 | r, err := b.call("getchaintips", nil) 255 | if err != nil { 256 | return 257 | } 258 | 259 | if r.Err != nil { 260 | rr := r.Err.(map[string]interface{}) 261 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 262 | return 263 | } 264 | 265 | err = json.Unmarshal(r.Result, &tips) 266 | return 267 | } 268 | 269 | // GetMempoolInfo comment 270 | func (b *Bitcoind) GetMempoolInfo() (info MempoolInfo, err error) { 271 | r, err := b.call("getmempoolinfo", nil) 272 | if err != nil { 273 | return 274 | } 275 | 276 | if r.Err != nil { 277 | rr := r.Err.(map[string]interface{}) 278 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 279 | return 280 | } 281 | 282 | err = json.Unmarshal(r.Result, &info) 283 | return 284 | } 285 | 286 | // GetMempoolEntry returns the entry in the current mempool for a specific tx id 287 | func (b *Bitcoind) GetMempoolEntry(txid string) (entry MempoolEntry, err error) { 288 | p := []interface{}{txid} 289 | r, err := b.call("getmempoolentry", p) 290 | if err != nil { 291 | return 292 | } 293 | 294 | if r.Err != nil { 295 | rr := r.Err.(map[string]interface{}) 296 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 297 | return 298 | } 299 | 300 | err = json.Unmarshal(r.Result, &entry) 301 | return 302 | } 303 | 304 | // GetRawMempool returns the number of connections to other nodes. 305 | func (b *Bitcoind) GetRawMempool(details bool) (raw []byte, err error) { 306 | p := []interface{}{details} 307 | r, err := b.call("getrawmempool", p) 308 | if err != nil { 309 | return 310 | } 311 | 312 | if r.Err != nil { 313 | rr := r.Err.(map[string]interface{}) 314 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 315 | return 316 | } 317 | 318 | //err = json.Unmarshal(r.Result, &raw) 319 | raw, err = json.Marshal(r.Result) 320 | return 321 | } 322 | 323 | // GetRawNonFinalMempool returns all transaction ids in the non-final memory pool as a json array of string transaction ids. 324 | func (b *Bitcoind) GetRawNonFinalMempool() ([]string, error) { 325 | r, err := b.call("getrawnonfinalmempool", nil) 326 | if err != nil { 327 | return nil, err 328 | } 329 | 330 | var ids []string 331 | _ = json.Unmarshal(r.Result, &ids) 332 | 333 | return ids, nil 334 | } 335 | 336 | // GetMempoolAncestors if txid is in the mempool, returns all in-mempool ancestors.. 337 | func (b *Bitcoind) GetMempoolAncestors(txid string, details bool) (raw []byte, err error) { 338 | p := []interface{}{txid} 339 | r, err := b.call("getmempoolancestors", p) 340 | if err != nil { 341 | return 342 | } 343 | 344 | if r.Err != nil { 345 | rr := r.Err.(map[string]interface{}) 346 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 347 | return 348 | } 349 | 350 | raw, err = json.Marshal(r.Result) 351 | return 352 | } 353 | 354 | // GetMempoolDescendants if txid is in the mempool, returns all in-mempool descendants.. 355 | func (b *Bitcoind) GetMempoolDescendants(txid string, details bool) (raw []byte, err error) { 356 | p := []interface{}{txid} 357 | r, err := b.call("getmempooldescendants", p) 358 | if err != nil { 359 | return 360 | } 361 | 362 | if r.Err != nil { 363 | rr := r.Err.(map[string]interface{}) 364 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 365 | return 366 | } 367 | 368 | raw, err = json.Marshal(r.Result) 369 | return 370 | } 371 | 372 | // GetChainTxStats returns the number of connections to other nodes. 373 | func (b *Bitcoind) GetChainTxStats(blockcount int) (stats ChainTXStats, err error) { 374 | p := []interface{}{blockcount} 375 | r, err := b.call("getchaintxstats", p) 376 | if err != nil { 377 | return 378 | } 379 | 380 | if r.Err != nil { 381 | rr := r.Err.(map[string]interface{}) 382 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 383 | return 384 | } 385 | 386 | err = json.Unmarshal(r.Result, &stats) 387 | return 388 | } 389 | 390 | // ValidateAddress returns the number of connections to other nodes. 391 | func (b *Bitcoind) ValidateAddress(address string) (addr Address, err error) { 392 | p := []interface{}{address} 393 | r, err := b.call("validateaddress", p) 394 | if err != nil { 395 | return 396 | } 397 | 398 | if r.Err != nil { 399 | rr := r.Err.(map[string]interface{}) 400 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 401 | return 402 | } 403 | 404 | err = json.Unmarshal(r.Result, &addr) 405 | return 406 | } 407 | 408 | // GetHelp returns the number of connections to other nodes. 409 | func (b *Bitcoind) GetHelp() (j []byte, err error) { 410 | r, err := b.call("help", nil) 411 | if err != nil { 412 | return 413 | } 414 | j, err = json.Marshal(r.Result) 415 | 416 | return 417 | } 418 | 419 | // GetBestBlockHash comment 420 | func (b *Bitcoind) GetBestBlockHash() (hash string, err error) { 421 | r, err := b.call("getbestblockhash", nil) 422 | if err != nil { 423 | return "", err 424 | } 425 | if err := json.Unmarshal(r.Result, &hash); err != nil { 426 | return "", err 427 | } 428 | return 429 | } 430 | 431 | // GetBlockHash comment 432 | func (b *Bitcoind) GetBlockHash(blockHeight int) (blockHash string, err error) { 433 | p := []interface{}{blockHeight} 434 | r, err := b.call("getblockhash", p) 435 | if err != nil { 436 | return "", err 437 | } 438 | 439 | if err := json.Unmarshal(r.Result, &blockHash); err != nil { 440 | return "", err 441 | } 442 | 443 | return 444 | } 445 | 446 | func keyFuncSendRawTransaction(method string, params []interface{}) string { 447 | var b strings.Builder 448 | 449 | b.WriteString(method) 450 | b.WriteRune('-') 451 | b.WriteString(params[0].(string)) 452 | 453 | return b.String() 454 | } 455 | 456 | func (b *Bitcoind) SendRawTransaction(hex string) (txid string, err error) { 457 | r, err := b.callWithKeyFunc("sendrawtransaction", []interface{}{hex}, keyFuncSendRawTransaction) 458 | if err != nil { 459 | return "", err 460 | } 461 | 462 | if err := json.Unmarshal(r.Result, &txid); err != nil { 463 | return "", err 464 | } 465 | 466 | return 467 | } 468 | 469 | func (b *Bitcoind) SendRawTransactionWithoutFeeCheck(hex string) (txid string, err error) { 470 | 471 | // Doing this function is 4 times faster than the normal fmt.Sprintf("%s|%v", method, params). As we know that 472 | // we will always pass (string, bool, bool) as the params, we can avoid the cost of reflection. 473 | keyFunc := func(method string, params []interface{}) string { 474 | var s strings.Builder 475 | 476 | s.WriteString(method) 477 | s.WriteRune('-') 478 | s.WriteString(params[0].(string)) 479 | s.WriteRune('|') 480 | if params[1].(bool) { 481 | s.WriteString("T") 482 | } else { 483 | s.WriteString("F") 484 | } 485 | if params[2].(bool) { 486 | s.WriteRune('T') 487 | } else { 488 | s.WriteRune('F') 489 | } 490 | 491 | return s.String() 492 | } 493 | 494 | r, err := b.callWithKeyFunc("sendrawtransaction", []interface{}{hex, false, true}, keyFunc) 495 | if err != nil { 496 | return "", err 497 | } 498 | 499 | if err := json.Unmarshal(r.Result, &txid); err != nil { 500 | return "", err 501 | } 502 | 503 | return 504 | } 505 | 506 | type BatchedTransaction struct { 507 | Hex string `json:"hex"` 508 | AllowHighFees bool `json:"allowhighfees"` 509 | DontCheckFee bool `json:"dontcheckfee"` 510 | ListUnconfirmedAncestors bool `json:"listunconfirmedancestors"` 511 | Config map[string]interface{} `json:"config,omitempty"` 512 | } 513 | 514 | type TxResponse struct { 515 | TxID string `json:"txid"` 516 | RejectReason string `json:"reject_reason"` 517 | } 518 | 519 | type BatchResults struct { 520 | Known []string `json:"known"` 521 | Evicted []string `json:"evicted"` 522 | Invalid []*TxResponse `json:"invalid"` 523 | Unconfirmed []*TxResponse `json:"unconfirmed"` 524 | } 525 | 526 | func (b *Bitcoind) SendRawTransactions(batchedTransactions []*BatchedTransaction, config map[string]interface{}) (*BatchResults, error) { 527 | r, err := b.call("sendrawtransactions", []interface{}{batchedTransactions, config}) 528 | if err != nil { 529 | return nil, err 530 | } 531 | 532 | var res BatchResults 533 | 534 | if err := json.Unmarshal(r.Result, &res); err != nil { 535 | return nil, err 536 | } 537 | 538 | return &res, nil 539 | } 540 | 541 | func (b *Bitcoind) SendRawTransactionWithoutFeeCheckOrScriptCheck(raw string) (string, error) { 542 | 543 | transactions := []*BatchedTransaction{{ 544 | Hex: raw, 545 | AllowHighFees: false, 546 | DontCheckFee: true, 547 | ListUnconfirmedAncestors: false, 548 | Config: map[string]interface{}{"maxscriptsizepolicy": 50_000_000}, 549 | }} 550 | 551 | r, err := b.call("sendrawtransactions", []interface{}{transactions}) 552 | if err != nil { 553 | return "", err 554 | } 555 | 556 | var res BatchResults 557 | 558 | if err := json.Unmarshal(r.Result, &res); err != nil { 559 | return "", err 560 | } 561 | 562 | if len(res.Known) > 0 { 563 | return res.Known[0], nil 564 | } else if len(res.Unconfirmed) > 0 { 565 | return res.Unconfirmed[0].TxID, nil 566 | } else if len(res.Evicted) > 0 { 567 | return "", errors.New("Transaction evicted due to insufficient fees") 568 | } else if len(res.Invalid) > 0 { 569 | return "", fmt.Errorf("Transaction invalid: %s", res.Invalid[0].RejectReason) 570 | } else { 571 | // It seems that if the transaction is not listed in any of the above arrays, it is successful. Compute the txid 572 | 573 | b, _ := hex.DecodeString(raw) 574 | hash := cryptolib.ReverseBytes(cryptolib.Sha256d(b)) 575 | return hex.EncodeToString(hash), nil 576 | } 577 | } 578 | 579 | // SignRawTransaction comment 580 | func (b *Bitcoind) SignRawTransaction(hex string) (sr *SignRawTransactionResponse, err error) { 581 | r, err := b.call("signrawtransaction", []interface{}{hex}) 582 | if err != nil { 583 | return 584 | } 585 | 586 | if r.Err != nil { 587 | rr := r.Err.(map[string]interface{}) 588 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 589 | return 590 | } 591 | 592 | err = json.Unmarshal(r.Result, &sr) 593 | return 594 | } 595 | 596 | // GetBlock returns information about the block with the given hash. 597 | func (b *Bitcoind) GetBlock(blockHash string) (block *Block, err error) { 598 | r, err := b.call("getblock", []interface{}{blockHash}) 599 | 600 | if err != nil { 601 | return 602 | } 603 | 604 | if r.Err != nil { 605 | rr := r.Err.(map[string]interface{}) 606 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 607 | return 608 | } 609 | 610 | err = json.Unmarshal(r.Result, &block) 611 | return 612 | } 613 | 614 | // GetBlockStatsByHeight returns block stats from the given block height. 615 | func (b *Bitcoind) GetBlockStatsByHeight(blockHeight int) (block *BlockStats, err error) { 616 | r, err := b.call("getblockstatsbyheight", []interface{}{blockHeight}) 617 | if err != nil { 618 | return 619 | } 620 | 621 | if r.Err != nil { 622 | rr := r.Err.(map[string]interface{}) 623 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 624 | return 625 | } 626 | 627 | err = json.Unmarshal(r.Result, &block) 628 | return 629 | } 630 | 631 | // GetBlockStats returns block stats from the given block hash. 632 | func (b *Bitcoind) GetBlockStats(blockHash string) (block *BlockStats, err error) { 633 | r, err := b.call("getblockstats", []interface{}{blockHash}) 634 | if err != nil { 635 | return 636 | } 637 | 638 | if r.Err != nil { 639 | rr := r.Err.(map[string]interface{}) 640 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 641 | return 642 | } 643 | 644 | err = json.Unmarshal(r.Result, &block) 645 | return 646 | } 647 | 648 | func (b *Bitcoind) GetBlockByHeight(blockHeight int) (block *Block, err error) { 649 | r, err := b.call("getblockbyheight", []interface{}{blockHeight}) 650 | if err != nil { 651 | return 652 | } 653 | 654 | if r.Err != nil { 655 | rr := r.Err.(map[string]interface{}) 656 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 657 | return 658 | } 659 | 660 | err = json.Unmarshal(r.Result, &block) 661 | return 662 | } 663 | 664 | // GetRawBlock returns the raw bytes of the block with the given hash. 665 | func (b *Bitcoind) GetRawBlock(blockHash string) ([]byte, error) { 666 | r, err := b.call("getblock", []interface{}{blockHash, 0}) 667 | if err != nil { 668 | return nil, err 669 | } 670 | 671 | var rawHex string 672 | err = json.Unmarshal(r.Result, &rawHex) 673 | if err != nil { 674 | return nil, err 675 | } 676 | 677 | res, err := hex.DecodeString(rawHex) 678 | if err != nil { 679 | return nil, err 680 | } 681 | 682 | return res, nil 683 | } 684 | 685 | // GetRawBlockReader returns a reader of the block with the given hash. 686 | func (b *Bitcoind) GetRawBlockReader(blockHash string) (io.ReadCloser, error) { 687 | return b.read("getblock", []interface{}{blockHash, 0}) 688 | } 689 | 690 | func (b *Bitcoind) GetRawBlockRest(blockHash string) (io.ReadCloser, error) { 691 | resp, err := http.Get(fmt.Sprintf("%s/rest/block/%s.bin", b.client.serverAddr, blockHash)) 692 | if err != nil { 693 | return nil, fmt.Errorf("Could not GET block: %v", err) 694 | } 695 | 696 | if resp.StatusCode != 200 { 697 | defer resp.Body.Close() 698 | 699 | data, err := io.ReadAll(resp.Body) 700 | 701 | if err != nil { 702 | return nil, fmt.Errorf("failed to read response body: %w", err) 703 | } 704 | 705 | return nil, fmt.Errorf("ERROR: code %d: %s", resp.StatusCode, data) 706 | } 707 | 708 | return resp.Body, nil 709 | } 710 | 711 | // GetBlockOverview returns basic information about the block with the given hash. 712 | func (b *Bitcoind) GetBlockOverview(blockHash string) (block *BlockOverview, err error) { 713 | r, err := b.call("getblock", []interface{}{blockHash}) 714 | 715 | if err != nil { 716 | return 717 | } 718 | 719 | if r.Err != nil { 720 | rr := r.Err.(map[string]interface{}) 721 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 722 | return 723 | } 724 | 725 | err = json.Unmarshal(r.Result, &block) 726 | return 727 | } 728 | 729 | // GetBlockHeaderHex returns the block header hex for the given hash. 730 | func (b *Bitcoind) GetBlockHeaderHex(blockHash string) (blockHeader *string, err error) { 731 | r, err := b.call("getblockheader", []interface{}{blockHash, false}) 732 | 733 | if err != nil { 734 | return 735 | } 736 | 737 | if r.Err != nil { 738 | rr := r.Err.(map[string]interface{}) 739 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 740 | return 741 | } 742 | 743 | err = json.Unmarshal(r.Result, &blockHeader) 744 | return 745 | } 746 | 747 | // GetBlockHeader returns the block header for the given hash. 748 | func (b *Bitcoind) GetBlockHeader(blockHash string) (blockHeader *BlockHeader, err error) { 749 | r, err := b.call("getblockheader", []interface{}{blockHash}) 750 | 751 | if err != nil { 752 | return 753 | } 754 | 755 | if r.Err != nil { 756 | rr := r.Err.(map[string]interface{}) 757 | err = fmt.Errorf("ERROR %s: %s", rr["code"], rr["message"]) 758 | return 759 | } 760 | 761 | err = json.Unmarshal(r.Result, &blockHeader) 762 | return 763 | } 764 | 765 | // GetBlockHex returns information about the block with the given hash. 766 | func (b *Bitcoind) GetBlockHex(blockHash string) (raw *string, err error) { 767 | r, err := b.call("getblock", []interface{}{blockHash, 0}) 768 | if err != nil { 769 | return 770 | } 771 | 772 | err = json.Unmarshal(r.Result, &raw) 773 | return 774 | } 775 | 776 | // GetBlockHeaderAndCoinbase returns information about the block with the given hash. 777 | func (b *Bitcoind) GetBlockHeaderAndCoinbase(blockHash string) (blockHeaderAndCoinbase *BlockHeaderAndCoinbase, err error) { 778 | r, err := b.call("getblock", []interface{}{blockHash, 3}) 779 | if err != nil { 780 | return 781 | } 782 | 783 | err = json.Unmarshal(r.Result, &blockHeaderAndCoinbase) 784 | return 785 | } 786 | 787 | func keyFuncForGetRawTransaction(method string, params []interface{}) string { 788 | var b strings.Builder 789 | b.WriteString(method) 790 | b.WriteRune('-') 791 | b.WriteString(params[0].(string)) 792 | b.WriteRune('|') 793 | if params[1].(int) == 0 { 794 | b.WriteRune('0') 795 | } else { 796 | b.WriteRune('1') 797 | } 798 | 799 | return b.String() 800 | } 801 | 802 | // GetRawTransaction returns raw transaction representation for given transaction id. 803 | func (b *Bitcoind) GetRawTransaction(txID string) (rawTx *RawTransaction, err error) { 804 | if txID == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" { 805 | // This is the genesis coinbase transaction and cannot be retrieved in this way. 806 | return &RawTransaction{ 807 | Hex: "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", 808 | TxID: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 809 | Hash: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 810 | Version: 1, 811 | Size: 204, 812 | LockTime: 0, 813 | Vin: []*Vin{ 814 | { 815 | Coinbase: "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", 816 | Sequence: 4294967295, 817 | }, 818 | }, 819 | Vout: []*Vout{ 820 | { 821 | Value: 50.00000000, 822 | N: 0, 823 | ScriptPubKey: ScriptPubKey{ 824 | ASM: "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG", 825 | Hex: "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", 826 | ReqSigs: 1, 827 | Type: "pubkey", 828 | Addresses: []string{"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"}, 829 | }, 830 | }, 831 | }, 832 | BlockHash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 833 | Time: 1231006505, 834 | Blocktime: 1231006505, 835 | }, nil 836 | } 837 | 838 | r, err := b.callWithKeyFunc("getrawtransaction", []interface{}{txID, 1}, keyFuncForGetRawTransaction) 839 | if err != nil { 840 | return 841 | } 842 | err = json.Unmarshal(r.Result, &rawTx) 843 | return 844 | } 845 | 846 | // GetRawTransactionHex returns raw transaction representation for given transaction id. 847 | func (b *Bitcoind) GetRawTransactionHex(txID string) (rawTx *string, err error) { 848 | if txID == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" { 849 | // This is the genesis coinbase transaction and cannot be retrieved in this way. 850 | genesisHex := "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000" 851 | return &genesisHex, nil 852 | } 853 | 854 | r, err := b.callWithKeyFunc("getrawtransaction", []interface{}{txID, 0}, keyFuncForGetRawTransaction) 855 | if err != nil { 856 | return 857 | } 858 | 859 | err = json.Unmarshal(r.Result, &rawTx) 860 | return 861 | } 862 | 863 | func (b *Bitcoind) GetRawTransactionRest(txid string) (io.ReadCloser, error) { 864 | resp, err := http.Get(fmt.Sprintf("%s/rest/tx/%s.bin", b.client.serverAddr, txid)) 865 | if err != nil { 866 | return nil, fmt.Errorf("Could not GET tx: %v", err) 867 | } 868 | 869 | if resp.StatusCode != 200 { 870 | defer resp.Body.Close() 871 | 872 | data, err := io.ReadAll(resp.Body) 873 | 874 | if err != nil { 875 | return nil, fmt.Errorf("failed to read response body: %w", err) 876 | } 877 | 878 | return nil, fmt.Errorf("ERROR: code %d: %s", resp.StatusCode, data) 879 | } 880 | 881 | return resp.Body, nil 882 | } 883 | 884 | // GetBlockTemplate comment 885 | func (b *Bitcoind) GetBlockTemplate(includeSegwit bool) (template *BlockTemplate, err error) { 886 | params := gbtParams{} 887 | if includeSegwit { 888 | params = gbtParams{ 889 | Mode: "", 890 | Capabilities: []string{}, 891 | Rules: []string{"segwit"}, 892 | } 893 | } 894 | 895 | r, err := b.call("getblocktemplate", []interface{}{params}) 896 | if err != nil { 897 | return nil, err 898 | } 899 | 900 | if err := json.Unmarshal(r.Result, &template); err != nil { 901 | return nil, err 902 | } 903 | 904 | return 905 | } 906 | 907 | // GetMiningCandidate comment 908 | func (b *Bitcoind) GetMiningCandidate() (template *MiningCandidate, err error) { 909 | 910 | r, err := b.call("getminingcandidate", nil) 911 | if err != nil { 912 | return nil, err 913 | } 914 | 915 | if err := json.Unmarshal(r.Result, &template); err != nil { 916 | return nil, err 917 | } 918 | 919 | return 920 | } 921 | 922 | // SubmitBlock comment 923 | func (b *Bitcoind) SubmitBlock(hexData string) (result string, err error) { 924 | r, err := b.client.call("submitblock", []interface{}{hexData}) 925 | if err != nil || r.Err != nil || string(r.Result) != "null" { 926 | msg := fmt.Sprintf("******* BLOCK SUBMIT FAILED with error: %+v and result: %s\n", err, string(r.Result)) 927 | return "", errors.New(msg) 928 | } 929 | 930 | return string(r.Result), nil 931 | } 932 | 933 | // SubmitMiningSolution comment 934 | func (b *Bitcoind) SubmitMiningSolution(miningCandidateID string, nonce uint32, coinbase string, time uint32, version uint32) (result string, err error) { 935 | params := submitMiningSolutionParams{ 936 | ID: miningCandidateID, 937 | Nonce: nonce, 938 | Coinbase: coinbase, 939 | Time: time, 940 | Version: version, 941 | } 942 | 943 | r, err := b.client.call("submitminingsolution", []interface{}{params}) 944 | if (err != nil && err.Error() != "") || r.Err != nil || (string(r.Result) != "null" && string(r.Result) != "true") { 945 | msg := fmt.Sprintf("******* BLOCK SUBMIT FAILED with error: %+v and result: %s\n", err, string(r.Result)) 946 | return "", errors.New(msg) 947 | } 948 | 949 | return string(r.Result), nil 950 | } 951 | 952 | // GetDifficulty comment 953 | func (b *Bitcoind) GetDifficulty() (difficulty float64, err error) { 954 | r, err := b.call("getdifficulty", nil) 955 | if err != nil { 956 | return 0.0, err 957 | } 958 | 959 | difficulty, err = strconv.ParseFloat(string(r.Result), 64) 960 | return 961 | } 962 | 963 | // DecodeRawTransaction comment 964 | func (b *Bitcoind) DecodeRawTransaction(txHex string) (string, error) { 965 | r, err := b.call("decoderawtransaction", []interface{}{txHex}) 966 | if err != nil { 967 | return "", err 968 | } 969 | 970 | return string(r.Result), nil 971 | } 972 | 973 | func keyFuncForGetTxOut(method string, params []interface{}) string { 974 | var b strings.Builder 975 | b.Grow(85) // "gettxout" = 9, "-" = 1, {txid} = 64, "|" = 1, int = 8 bytes "|" = 1, "T" = 1 976 | 977 | if params[2].(bool) { 978 | fmt.Fprintf(&b, "%s-%s|%d|T", method, params[0].(string), params[1].(int)) 979 | } else { 980 | fmt.Fprintf(&b, "%s-%s|%d|F", method, params[0].(string), params[1].(int)) 981 | } 982 | 983 | return b.String() 984 | } 985 | 986 | func (b *Bitcoind) GetTxOut(txHex string, vout int, includeMempool bool) (res *TXOut, err error) { 987 | r, err := b.callWithKeyFunc("gettxout", []interface{}{txHex, vout, includeMempool}, keyFuncForGetTxOut) 988 | if err != nil { 989 | return 990 | } 991 | 992 | _ = json.Unmarshal(r.Result, &res) 993 | 994 | return 995 | } 996 | 997 | func (b *Bitcoind) GetMerkleProof(blockhash string, txID string) (merkleProof *MerkleProof, err error) { 998 | 999 | r, err := b.call("getmerkleproof2", []interface{}{blockhash, txID}) 1000 | if err != nil { 1001 | return nil, err 1002 | } 1003 | 1004 | err = json.Unmarshal(r.Result, &merkleProof) 1005 | return 1006 | } 1007 | 1008 | // ListUnspent comment 1009 | func (b *Bitcoind) ListUnspent(addresses []string) (res []*UnspentTransaction, err error) { 1010 | var minConf uint32 = 0 1011 | var maxConf uint32 = 9999999 1012 | 1013 | r, err := b.call("listunspent", []interface{}{minConf, maxConf, addresses}) 1014 | if err != nil { 1015 | return 1016 | } 1017 | 1018 | _ = json.Unmarshal(r.Result, &res) 1019 | 1020 | for _, utxo := range res { 1021 | if utxo.Amount > 0 && utxo.Satoshis == 0 { 1022 | utxo.Satoshis = uint64(utxo.Amount * 100000000) 1023 | } 1024 | } 1025 | 1026 | return 1027 | } 1028 | 1029 | // SendToAddress comment 1030 | func (b *Bitcoind) SendToAddress(address string, amount float64) (string, error) { 1031 | r, err := b.call("sendtoaddress", []interface{}{address, amount}) 1032 | if err != nil { 1033 | return "", err 1034 | } 1035 | 1036 | var txid string 1037 | _ = json.Unmarshal(r.Result, &txid) 1038 | 1039 | return txid, nil 1040 | } 1041 | 1042 | // Generate for regtest 1043 | func (b *Bitcoind) Generate(amount float64) ([]string, error) { 1044 | r, err := b.call("generate", []interface{}{amount}) 1045 | if err != nil { 1046 | return nil, err 1047 | } 1048 | 1049 | var hashes []string 1050 | _ = json.Unmarshal(r.Result, &hashes) 1051 | 1052 | return hashes, nil 1053 | } 1054 | 1055 | // GenerateToAddress for regtest 1056 | func (b *Bitcoind) GenerateToAddress(amount float64, address string) ([]string, error) { 1057 | r, err := b.call("generatetoaddress", []interface{}{amount, address}) 1058 | if err != nil { 1059 | return nil, err 1060 | } 1061 | 1062 | var hashes []string 1063 | _ = json.Unmarshal(r.Result, &hashes) 1064 | 1065 | return hashes, nil 1066 | } 1067 | 1068 | func (b *Bitcoind) GetNewAddress() (string, error) { 1069 | r, err := b.call("getnewaddress", nil) 1070 | if err != nil { 1071 | return "", err 1072 | } 1073 | 1074 | var address string 1075 | _ = json.Unmarshal(r.Result, &address) 1076 | 1077 | return address, nil 1078 | } 1079 | 1080 | func (b *Bitcoind) DumpPrivKey(address string) (string, error) { 1081 | r, err := b.call("dumpprivkey", []interface{}{address}) 1082 | if err != nil { 1083 | return "", err 1084 | } 1085 | 1086 | var privKey string 1087 | _ = json.Unmarshal(r.Result, &privKey) 1088 | 1089 | return privKey, nil 1090 | } 1091 | 1092 | func (b *Bitcoind) SetAccount(address, account string) error { 1093 | _, err := b.call("setaccount", []interface{}{address, account}) 1094 | return err 1095 | } 1096 | --------------------------------------------------------------------------------