From 9e35758653e28b896062295707c9215252e0b979 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 31 Dec 2020 13:54:42 +0300 Subject: [PATCH] rpc: fix gettxout result for already spent outputs As per C# documentation [1]: If the transaction output is already spent, the result value will be null . [1]: https://docs.neo.org/docs/en-us/reference/rpc/latest-version/api/gettxout.html --- pkg/core/state/unspent_coin.go | 5 +++++ pkg/core/state/unspent_coin_test.go | 2 ++ pkg/rpc/server/server.go | 14 ++++++++------ pkg/rpc/server/server_test.go | 12 ++++++++++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pkg/core/state/unspent_coin.go b/pkg/core/state/unspent_coin.go index a0f1e635e..b474f1151 100644 --- a/pkg/core/state/unspent_coin.go +++ b/pkg/core/state/unspent_coin.go @@ -43,6 +43,11 @@ func (s *UnspentCoin) EncodeBinary(bw *io.BinWriter) { func (s *UnspentCoin) DecodeBinary(br *io.BinReader) { s.Height = br.ReadU32LE() br.ReadArray(&s.States) + if br.Err == nil { + for i := range s.States { + s.States[i].Output.Position = i + } + } } // EncodeBinary implements Serializable interface. diff --git a/pkg/core/state/unspent_coin_test.go b/pkg/core/state/unspent_coin_test.go index 51e778ab7..1904e5fd0 100644 --- a/pkg/core/state/unspent_coin_test.go +++ b/pkg/core/state/unspent_coin_test.go @@ -27,6 +27,7 @@ func TestDecodeEncodeUnspentCoin(t *testing.T) { AssetID: random.Uint256(), Amount: util.Fixed8(420), ScriptHash: random.Uint160(), + Position: 1, }, SpendHeight: 0, State: CoinConfirmed, @@ -36,6 +37,7 @@ func TestDecodeEncodeUnspentCoin(t *testing.T) { AssetID: random.Uint256(), Amount: util.Fixed8(4200), ScriptHash: random.Uint160(), + Position: 2, }, SpendHeight: 111000, State: CoinSpent & CoinClaimed, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 6eabd408d..c2b6e3900 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -1207,17 +1207,19 @@ func (s *Server) getTxOut(ps request.Params) (interface{}, *response.Error) { return nil, response.ErrInvalidParams } - tx, _, err := s.chain.GetTransaction(h) - if err != nil { - return nil, response.NewInvalidParamsError(err.Error(), err) + ucs := s.chain.GetUnspentCoinState(h) + if ucs == nil { + return nil, response.NewInvalidParamsError("invalid tx hash", errors.New("unknown")) } - if num >= len(tx.Outputs) { + if num >= len(ucs.States) { return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index")) } - out := tx.Outputs[num] - return result.NewTxOutput(&out), nil + if ucs.States[num].State&state.CoinSpent != 0 { + return nil, nil + } + return result.NewTxOutput(&ucs.States[num].Output), nil } // getContractState returns contract state (contract information, according to the contract script hash). diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index fb49d19bf..4b544610c 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1204,13 +1204,21 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] `"`+tx.Hash().StringLE()+`"`, 0) body := doRPCCall(rpc, httpSrv.URL, t) res := checkErrGetResult(t, body, false) + assert.Equal(t, "null", string(res)) // already spent + + block, _ = chain.GetBlock(e.chain.GetHeaderHash(1)) + tx = block.Transactions[1] + rpc = fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "gettxout", "params": [%s, %d]}"`, + `"`+tx.Hash().StringLE()+`"`, 1) // Neo remainder in txMoveNeo + body = doRPCCall(rpc, httpSrv.URL, t) + res = checkErrGetResult(t, body, false) var txOut result.TransactionOutput err := json.Unmarshal(res, &txOut) require.NoErrorf(t, err, "could not parse response: %s", res) - assert.Equal(t, 0, txOut.N) + assert.Equal(t, 1, txOut.N) assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset) - assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value) + assert.Equal(t, util.Fixed8FromInt64(1000), txOut.Value) assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address) })