diff --git a/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.py b/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.py index c7cfc8a70..5d1b37cf2 100644 --- a/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.py +++ b/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.py @@ -262,7 +262,7 @@ def GetAllSpentCoins(self): def GetUnspent(self, hash, index): sn = self._db.snapshot() - coins = DBCollection(self._db, sn, DBPrefix.ST_SpentCoin, UnspentCoinState) + coins = DBCollection(self._db, sn, DBPrefix.ST_Coin, UnspentCoinState) state = coins.TryGet(hash) diff --git a/neo/api/JSONRPC/JsonRpcApi.py b/neo/api/JSONRPC/JsonRpcApi.py index 8da69ac94..06da461b9 100644 --- a/neo/api/JSONRPC/JsonRpcApi.py +++ b/neo/api/JSONRPC/JsonRpcApi.py @@ -222,7 +222,13 @@ def json_rpc_method_handler(self, method, params): return None elif method == "gettxout": - raise NotImplementedError() + hash = params[0].encode('utf-8') + index = params[1] + utxo = Blockchain.Default().GetUnspent(hash, index) + if utxo: + return utxo.ToJson(index) + else: + return None elif method == "invoke": shash = UInt160.ParseString(params[0]) diff --git a/neo/api/JSONRPC/neo-cli-json-rpc-docs.md b/neo/api/JSONRPC/neo-cli-json-rpc-docs.md index 559470a83..becfacb92 100644 --- a/neo/api/JSONRPC/neo-cli-json-rpc-docs.md +++ b/neo/api/JSONRPC/neo-cli-json-rpc-docs.md @@ -39,6 +39,19 @@ On MainNet there are actually entries, each of which has this format: `0xde3bc1d curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "getversion", "params": [] }' { "jsonrpc": "2.0", "id": 5, "result": { "port": 20333, "nonce": 771199013, "useragent": "/NEO:2.6.0/" } } +## `gettxout` + + curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "gettxout", "params": ["0ff23561c611ccda65470c9a4a5f1be31f2f4f61b98c75d051e1a72e85a302eb", 1] }' + {"jsonrpc":"2.0","id":5,"result":{"n":1,"asset":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","value":"25","address":"AHYb3ySrHbhzouZ81ZMnCf8c7zYaoDg64x"} + +### secondary unspent showing float "value" vs the above int value + curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "gettxout", "params": ["9c9f2c430c3cfb805e8c22d0a7778a60ce7792fad52ffe9b34f56de8e2c1d2e6", 1] }' + {"jsonrpc":"2.0","id":5,"result":{"n":1,"asset":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","value":"2609.997813","address":"ASs7BiaRa9Z2NnJfvf7a4SZ7ciPLiPWefJ"}} + +### when querying an already spent output + curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "gettxout", "params": ["0ff23561c611ccda65470c9a4a5f1be31f2f4f61b98c75d051e1a72e85a302eb", 0] }' + {"jsonrpc":"2.0","id":5,"result":null} + ## `validateaddress` curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "validateaddress", "params": ["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"] }' {"jsonrpc":"2.0","id":5,"result":{"address":"AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i","isvalid":true}} @@ -49,4 +62,4 @@ On MainNet there are actually entries, each of which has this format: `0xde3bc1d ### with completely invalid argument curl -X POST http://seed2.neo.org:20332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "validateaddress", "params": [] }' - {"jsonrpc":"2.0","id":5,"error":{"code":-2146233086,"message":"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index"}} \ No newline at end of file + {"jsonrpc":"2.0","id":5,"error":{"code":-2146233086,"message":"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index"}} diff --git a/neo/api/JSONRPC/test_json_rpc_api.py b/neo/api/JSONRPC/test_json_rpc_api.py index 72093a470..bb23db4c6 100644 --- a/neo/api/JSONRPC/test_json_rpc_api.py +++ b/neo/api/JSONRPC/test_json_rpc_api.py @@ -374,3 +374,42 @@ def test_get_unspents(self): u = UInt256.ParseString('0ff23561c611ccda65470c9a4a5f1be31f2f4f61b98c75d051e1a72e85a302eb') unspents = GetBlockchain().GetAllUnspent(u) self.assertEqual(len(unspents), 1) + + def test_gettxout(self): + # block 730901 - 2 transactions + # output with index 0 is spent, so should return an error + + txid = '0ff23561c611ccda65470c9a4a5f1be31f2f4f61b98c75d051e1a72e85a302eb' + output_index = 0 + req = self._gen_rpc_req("gettxout", params=[txid, output_index]) + mock_req = mock_request(json.dumps(req).encode("utf-8")) + res = json.loads(self.app.home(mock_req)) + # will return `null` if not found + self.assertEqual(None, res["result"]) + + # output with index 1 is unspent, so should return valid values + txid = '0ff23561c611ccda65470c9a4a5f1be31f2f4f61b98c75d051e1a72e85a302eb' + output_index = 1 + req = self._gen_rpc_req("gettxout", params=[txid, output_index]) + mock_req = mock_request(json.dumps(req).encode("utf-8")) + res = json.loads(self.app.home(mock_req)) + + expected_asset = '0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7' + expected_value = "25" + expected_address = 'AHYb3ySrHbhzouZ81ZMnCf8c7zYaoDg64x' + + self.assertEqual(output_index, res["result"]["n"]) + self.assertEqual(expected_address, res["result"]["address"]) + self.assertEqual(expected_asset, res["result"]["asset"]) + self.assertEqual(expected_value, res["result"]["value"]) + + # now test for a different block (730848) with a floating value + txid = '9c9f2c430c3cfb805e8c22d0a7778a60ce7792fad52ffe9b34f56de8e2c1d2e6' + output_index = 1 # index 0 is spent, 0 is unspent + req = self._gen_rpc_req("gettxout", params=[txid, output_index]) + mock_req = mock_request(json.dumps(req).encode("utf-8")) + res = json.loads(self.app.home(mock_req)) + + expected_value = "2609.997813" + self.assertEqual(output_index, res["result"]["n"]) + self.assertEqual(expected_value, res["result"]["value"])