From bd49e9cbc3290cd8a024a21ab93049f1e7d33d06 Mon Sep 17 00:00:00 2001 From: Loong Date: Fri, 4 Sep 2020 14:01:30 +1000 Subject: [PATCH] add unspentoutput method to utxo clients --- api/utxo/utxo.go | 10 +++++++++- chain/bitcoin/bitcoin.go | 36 ++++++++++++++++++++++++++++++++++- chain/bitcoin/bitcoin_test.go | 9 ++++++++- go.sum | 9 +++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/api/utxo/utxo.go b/api/utxo/utxo.go index 8ae95a2d..11e0c872 100644 --- a/api/utxo/utxo.go +++ b/api/utxo/utxo.go @@ -83,9 +83,17 @@ type Client interface { // Output returns the transaction output identified by the given outpoint. // It also returns the number of confirmations for the output. If the output // cannot be found before the context is done, or the output is invalid, - // then an error should be returned. + // then an error should be returned. This method will not error, even if the + // output has been spent. Output(context.Context, Outpoint) (Output, pack.U64, error) + // UnspentOutput returns the unspent transaction output identified by the + // given outpoint. It also returns the number of confirmations for the + // output. If the output cannot be found before the context is done, the + // output is invalid, or the output has been spent, then an error should be + // returned. + UnspentOutput(context.Context, Outpoint) (Output, pack.U64, error) + // SubmitTx to the underlying chain. If the transaction cannot be found // before the context is done, or the transaction is invalid, then an error // should be returned. diff --git a/chain/bitcoin/bitcoin.go b/chain/bitcoin/bitcoin.go index 6840651d..ca66dc25 100644 --- a/chain/bitcoin/bitcoin.go +++ b/chain/bitcoin/bitcoin.go @@ -111,7 +111,7 @@ func (client *client) Output(ctx context.Context, outpoint utxo.Outpoint) (utxo. hash := chainhash.Hash{} copy(hash[:], outpoint.Hash) if err := client.send(ctx, &resp, "getrawtransaction", hash.String(), 1); err != nil { - return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad \"gettxout\": %v", err) + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad \"getrawtransaction\": %v", err) } if outpoint.Index.Uint32() >= uint32(len(resp.Vout)) { return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad index: %v is out of range", outpoint.Index) @@ -136,6 +136,40 @@ func (client *client) Output(ctx context.Context, outpoint utxo.Outpoint) (utxo. return output, pack.NewU64(resp.Confirmations), nil } +// UnspentOutput returns the unspent transaction output identified by the +// given outpoint. It also returns the number of confirmations for the +// output. If the output cannot be found before the context is done, the +// output is invalid, or the output has been spent, then an error should be +// returned. +func (client *client) UnspentOutput(ctx context.Context, outpoint utxo.Outpoint) (utxo.Output, pack.U64, error) { + resp := btcjson.GetTxOutResult{} + hash := chainhash.Hash{} + copy(hash[:], outpoint.Hash) + if err := client.send(ctx, &resp, "gettxout", hash.String(), outpoint.Index.Uint32()); err != nil { + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad \"gettxout\": %v", err) + } + amount, err := btcutil.NewAmount(resp.Value) + if err != nil { + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad amount: %v", err) + } + if amount < 0 { + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad amount: %v", amount) + } + if resp.Confirmations < 0 { + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad confirmations: %v", resp.Confirmations) + } + pubKeyScript, err := hex.DecodeString(resp.ScriptPubKey.Hex) + if err != nil { + return utxo.Output{}, pack.NewU64(0), fmt.Errorf("bad pubkey script: %v", err) + } + output := utxo.Output{ + Outpoint: outpoint, + Value: pack.NewU256FromU64(pack.NewU64(uint64(amount))), + PubKeyScript: pack.NewBytes(pubKeyScript), + } + return output, pack.NewU64(uint64(resp.Confirmations)), nil +} + // SubmitTx to the Bitcoin network. func (client *client) SubmitTx(ctx context.Context, tx utxo.Tx) error { serial, err := tx.Serialize() diff --git a/chain/bitcoin/bitcoin_test.go b/chain/bitcoin/bitcoin_test.go index 690f70a6..a0a72bd6 100644 --- a/chain/bitcoin/bitcoin_test.go +++ b/chain/bitcoin/bitcoin_test.go @@ -59,6 +59,9 @@ var _ = Describe("Bitcoin", func() { output2, _, err := client.Output(context.Background(), output.Outpoint) Expect(err).ToNot(HaveOccurred()) Expect(reflect.DeepEqual(output, output2)).To(BeTrue()) + output2, _, err = client.UnspentOutput(context.Background(), output.Outpoint) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.DeepEqual(output, output2)).To(BeTrue()) // Build the transaction by consuming the outputs and spending // them to a set of recipients. @@ -122,11 +125,15 @@ var _ = Describe("Bitcoin", func() { confs, err := client.Confirmations(context.Background(), txHash) Expect(err).ToNot(HaveOccurred()) log.Printf(" %v/3 confirmations", confs) - if confs >= 3 { + if confs >= 1 { break } time.Sleep(10 * time.Second) } + ctxWithTimeout, cancelCtxWithTimeout := context.WithTimeout(context.Background(), time.Second) + defer cancelCtxWithTimeout() + _, _, err = client.UnspentOutput(ctxWithTimeout, output.Outpoint) + Expect(err).To(HaveOccurred()) // Check that we can load the output and that it is equal. // Otherwise, something strange is happening with the RPC diff --git a/go.sum b/go.sum index 690ddb94..9e5e089c 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6Ro github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= @@ -117,9 +118,13 @@ github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+q github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= @@ -172,6 +177,7 @@ github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3E github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= @@ -602,6 +608,7 @@ github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -609,6 +616,7 @@ github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20190830100107-3784a6c7c552/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -632,6 +640,7 @@ github.com/kilic/bls12-381 v0.0.0-20200607163746-32e1441c8a9f/go.mod h1:XXfR6YFC github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=