feat(network): implement GetUTxOByAddress local state query (#341)
This commit is contained in:
parent
04232c6a4c
commit
6f1b15269c
5 changed files with 241 additions and 13 deletions
|
|
@ -1,6 +1,13 @@
|
||||||
use pallas::network::{
|
use pallas::{
|
||||||
facades::NodeClient,
|
ledger::addresses::Address,
|
||||||
miniprotocols::{chainsync, localstate::queries_v16, Point, PRE_PRODUCTION_MAGIC},
|
network::{
|
||||||
|
facades::NodeClient,
|
||||||
|
miniprotocols::{
|
||||||
|
chainsync,
|
||||||
|
localstate::queries_v16::{self, Addr, Addrs},
|
||||||
|
Point, PRE_PRODUCTION_MAGIC,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
|
@ -28,6 +35,20 @@ async fn do_localstate_query(client: &mut NodeClient) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
info!("result: {:?}", result);
|
info!("result: {:?}", result);
|
||||||
|
|
||||||
|
let addrx = "addr_test1vr80076l3x5uw6n94nwhgmv7ssgy6muzf47ugn6z0l92rhg2mgtu0".to_string();
|
||||||
|
let addrx: Address = Address::from_bech32(&addrx).unwrap();
|
||||||
|
let addrx: Addr = addrx.to_vec().into();
|
||||||
|
|
||||||
|
let addry = "008c5bf0f2af6f1ef08bb3f6ec702dd16e1c514b7e1d12f7549b47db9f4d943c7af0aaec774757d4745d1a2c8dd3220e6ec2c9df23f757a2f8".to_string();
|
||||||
|
let addry: Address = Address::from_hex(&addry).unwrap();
|
||||||
|
let addry: Addr = addry.to_vec().into();
|
||||||
|
|
||||||
|
let addrs: Addrs = vec![addrx, addry];
|
||||||
|
let result = queries_v16::get_utxo_by_address(client, era, addrs)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
info!("result: {:?}", result);
|
||||||
|
|
||||||
client.send_release().await.unwrap();
|
client.send_release().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl<C, const N: usize> minicbor::Encode<C> for SkipCbor<N> {
|
||||||
/// canonicalization for isomorphic decoding / encoding operators, we use a Vec
|
/// canonicalization for isomorphic decoding / encoding operators, we use a Vec
|
||||||
/// as the underlaying struct for storage of the items (as opposed to a BTreeMap
|
/// as the underlaying struct for storage of the items (as opposed to a BTreeMap
|
||||||
/// or HashMap).
|
/// or HashMap).
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[serde(from = "Vec::<(K, V)>", into = "Vec::<(K, V)>")]
|
#[serde(from = "Vec::<(K, V)>", into = "Vec::<(K, V)>")]
|
||||||
pub enum KeyValuePairs<K, V>
|
pub enum KeyValuePairs<K, V>
|
||||||
where
|
where
|
||||||
|
|
@ -475,7 +475,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A uint structure that preserves original int length
|
/// A uint structure that preserves original int length
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord)]
|
#[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash)]
|
||||||
pub enum AnyUInt {
|
pub enum AnyUInt {
|
||||||
MajorByte(u8),
|
MajorByte(u8),
|
||||||
U8(u8),
|
U8(u8),
|
||||||
|
|
@ -881,7 +881,7 @@ impl fmt::Display for Bytes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Serialize, Deserialize, Clone, Copy, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord,
|
Serialize, Deserialize, Clone, Copy, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Hash,
|
||||||
)]
|
)]
|
||||||
#[cbor(transparent)]
|
#[cbor(transparent)]
|
||||||
#[serde(into = "i128")]
|
#[serde(into = "i128")]
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ impl Encode<()> for BlockQuery {
|
||||||
e.array(1)?;
|
e.array(1)?;
|
||||||
e.u16(5)?;
|
e.u16(5)?;
|
||||||
}
|
}
|
||||||
BlockQuery::GetUTxOByAddress(x) => {
|
BlockQuery::GetUTxOByAddress(addrs) => {
|
||||||
e.array(2)?;
|
e.array(2)?;
|
||||||
e.u16(6)?;
|
e.u16(6)?;
|
||||||
e.encode(x)?;
|
e.encode(addrs)?;
|
||||||
}
|
}
|
||||||
BlockQuery::GetUTxOWhole => {
|
BlockQuery::GetUTxOWhole => {
|
||||||
e.encode((7,))?;
|
e.encode((7,))?;
|
||||||
|
|
@ -129,7 +129,7 @@ impl<'b> Decode<'b, ()> for BlockQuery {
|
||||||
3 => Ok(Self::GetCurrentPParams),
|
3 => Ok(Self::GetCurrentPParams),
|
||||||
4 => Ok(Self::GetProposedPParamsUpdates),
|
4 => Ok(Self::GetProposedPParamsUpdates),
|
||||||
5 => Ok(Self::GetStakeDistribution),
|
5 => Ok(Self::GetStakeDistribution),
|
||||||
// 6 => Ok(Self::GetUTxOByAddress(())),
|
6 => Ok(Self::GetUTxOByAddress(d.decode()?)),
|
||||||
// 7 => Ok(Self::GetUTxOWhole),
|
// 7 => Ok(Self::GetUTxOWhole),
|
||||||
// 8 => Ok(Self::DebugEpochState),
|
// 8 => Ok(Self::DebugEpochState),
|
||||||
// 9 => Ok(Self::GetCBOR(())),
|
// 9 => Ok(Self::GetCBOR(())),
|
||||||
|
|
@ -264,3 +264,44 @@ impl<'b> Decode<'b, ()> for Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'b, C> minicbor::decode::Decode<'b, C> for Value {
|
||||||
|
fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
|
||||||
|
match d.datatype()? {
|
||||||
|
minicbor::data::Type::U8 => Ok(Value::Coin(d.decode_with(ctx)?)),
|
||||||
|
minicbor::data::Type::U16 => Ok(Value::Coin(d.decode_with(ctx)?)),
|
||||||
|
minicbor::data::Type::U32 => Ok(Value::Coin(d.decode_with(ctx)?)),
|
||||||
|
minicbor::data::Type::U64 => Ok(Value::Coin(d.decode_with(ctx)?)),
|
||||||
|
minicbor::data::Type::Array => {
|
||||||
|
d.array()?;
|
||||||
|
let coin = d.decode_with(ctx)?;
|
||||||
|
let multiasset = d.decode_with(ctx)?;
|
||||||
|
Ok(Value::Multiasset(coin, multiasset))
|
||||||
|
}
|
||||||
|
_ => Err(minicbor::decode::Error::message(
|
||||||
|
"unknown cbor data type for Value enum",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> minicbor::encode::Encode<C> for Value {
|
||||||
|
fn encode<W: minicbor::encode::Write>(
|
||||||
|
&self,
|
||||||
|
e: &mut minicbor::Encoder<W>,
|
||||||
|
ctx: &mut C,
|
||||||
|
) -> Result<(), minicbor::encode::Error<W::Error>> {
|
||||||
|
match self {
|
||||||
|
Value::Coin(coin) => {
|
||||||
|
e.encode_with(coin, ctx)?;
|
||||||
|
}
|
||||||
|
Value::Multiasset(coin, other) => {
|
||||||
|
e.array(2)?;
|
||||||
|
e.encode_with(coin, ctx)?;
|
||||||
|
e.encode_with(other, ctx)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// TODO: this should move to pallas::ledger crate at some point
|
// TODO: this should move to pallas::ledger crate at some point
|
||||||
|
|
||||||
|
use pallas_crypto::hash::Hash;
|
||||||
|
use std::hash::Hash as StdHash;
|
||||||
// required for derive attrs to work
|
// required for derive attrs to work
|
||||||
use pallas_codec::minicbor::{self};
|
use pallas_codec::minicbor::{self};
|
||||||
|
|
||||||
use pallas_codec::utils::{Bytes, KeyValuePairs};
|
use pallas_codec::utils::{AnyUInt, Bytes, KeyValuePairs, TagWrap};
|
||||||
use pallas_codec::{
|
use pallas_codec::{
|
||||||
minicbor::{Decode, Encode},
|
minicbor::{Decode, Encode},
|
||||||
utils::AnyCbor,
|
utils::AnyCbor,
|
||||||
|
|
@ -25,7 +27,7 @@ pub enum BlockQuery {
|
||||||
GetCurrentPParams,
|
GetCurrentPParams,
|
||||||
GetProposedPParamsUpdates,
|
GetProposedPParamsUpdates,
|
||||||
GetStakeDistribution,
|
GetStakeDistribution,
|
||||||
GetUTxOByAddress(AnyCbor),
|
GetUTxOByAddress(Addrs),
|
||||||
GetUTxOWhole,
|
GetUTxOWhole,
|
||||||
DebugEpochState,
|
DebugEpochState,
|
||||||
GetCBOR(AnyCbor),
|
GetCBOR(AnyCbor),
|
||||||
|
|
@ -69,6 +71,12 @@ pub enum Request {
|
||||||
GetChainPoint,
|
GetChainPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum Value {
|
||||||
|
Coin(Coin),
|
||||||
|
Multiasset(Coin, Multiasset<Coin>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Encode, Decode, PartialEq)]
|
#[derive(Debug, Encode, Decode, PartialEq)]
|
||||||
pub struct SystemStart {
|
pub struct SystemStart {
|
||||||
#[n(0)]
|
#[n(0)]
|
||||||
|
|
@ -105,6 +113,49 @@ pub struct Fraction {
|
||||||
pub dem: u64,
|
pub dem: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Addr = Bytes;
|
||||||
|
|
||||||
|
pub type Addrs = Vec<Addr>;
|
||||||
|
|
||||||
|
pub type Coin = AnyUInt;
|
||||||
|
|
||||||
|
pub type PolicyId = Hash<28>;
|
||||||
|
|
||||||
|
pub type AssetName = Bytes;
|
||||||
|
|
||||||
|
pub type Multiasset<A> = KeyValuePairs<PolicyId, KeyValuePairs<AssetName, A>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Encode, Decode, PartialEq, Clone)]
|
||||||
|
pub struct UTxOByAddress {
|
||||||
|
#[n(0)]
|
||||||
|
pub utxo: KeyValuePairs<UTxO, Values>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes CDDL -> #6.121([ * #6.121([ *datum ]) ])
|
||||||
|
pub type Datum = (Era, TagWrap<Bytes, 24>);
|
||||||
|
|
||||||
|
#[derive(Debug, Encode, Decode, PartialEq, Clone)]
|
||||||
|
#[cbor(map)]
|
||||||
|
pub struct Values {
|
||||||
|
#[n(0)]
|
||||||
|
pub address: Bytes,
|
||||||
|
|
||||||
|
#[n(1)]
|
||||||
|
pub amount: Value,
|
||||||
|
|
||||||
|
#[n(2)]
|
||||||
|
pub inline_datum: Option<Datum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Encode, Decode, PartialEq, Clone, StdHash, Eq)]
|
||||||
|
pub struct UTxO {
|
||||||
|
#[n(0)]
|
||||||
|
pub transaction_id: Hash<32>,
|
||||||
|
|
||||||
|
#[n(1)]
|
||||||
|
pub index: AnyUInt,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_chain_point(client: &mut Client) -> Result<Point, ClientError> {
|
pub async fn get_chain_point(client: &mut Client) -> Result<Point, ClientError> {
|
||||||
let query = Request::GetChainPoint;
|
let query = Request::GetChainPoint;
|
||||||
let result = client.query(query).await?;
|
let result = client.query(query).await?;
|
||||||
|
|
@ -148,3 +199,16 @@ pub async fn get_stake_distribution(
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_utxo_by_address(
|
||||||
|
client: &mut Client,
|
||||||
|
era: u16,
|
||||||
|
addrs: Addrs,
|
||||||
|
) -> Result<UTxOByAddress, ClientError> {
|
||||||
|
let query = BlockQuery::GetUTxOByAddress(addrs);
|
||||||
|
let query = LedgerQuery::BlockQuery(era, query);
|
||||||
|
let query = Request::LedgerQuery(query);
|
||||||
|
let result = client.query(query).await?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ use std::fs;
|
||||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use pallas_codec::utils::{AnyCbor, KeyValuePairs};
|
use pallas_codec::utils::{AnyCbor, AnyUInt, KeyValuePairs, TagWrap};
|
||||||
|
use pallas_crypto::hash::Hash;
|
||||||
use pallas_network::facades::{NodeClient, PeerClient, PeerServer};
|
use pallas_network::facades::{NodeClient, PeerClient, PeerServer};
|
||||||
use pallas_network::miniprotocols::blockfetch::BlockRequest;
|
use pallas_network::miniprotocols::blockfetch::BlockRequest;
|
||||||
use pallas_network::miniprotocols::chainsync::{ClientRequest, HeaderContent, Tip};
|
use pallas_network::miniprotocols::chainsync::{ClientRequest, HeaderContent, Tip};
|
||||||
use pallas_network::miniprotocols::handshake::n2n::VersionData;
|
use pallas_network::miniprotocols::handshake::n2n::VersionData;
|
||||||
|
use pallas_network::miniprotocols::localstate::queries_v16::{Addr, Addrs, Value};
|
||||||
use pallas_network::miniprotocols::localstate::ClientQueryRequest;
|
use pallas_network::miniprotocols::localstate::ClientQueryRequest;
|
||||||
use pallas_network::miniprotocols::{
|
use pallas_network::miniprotocols::{
|
||||||
blockfetch,
|
blockfetch,
|
||||||
|
|
@ -539,7 +541,59 @@ pub async fn local_state_query_server_and_client_happy_path() {
|
||||||
let pools = KeyValuePairs::from(pools);
|
let pools = KeyValuePairs::from(pools);
|
||||||
|
|
||||||
let result = AnyCbor::from_encode(localstate::queries_v16::StakeDistribution { pools });
|
let result = AnyCbor::from_encode(localstate::queries_v16::StakeDistribution { pools });
|
||||||
|
server.statequery().send_result(result).await.unwrap();
|
||||||
|
|
||||||
|
// server receives query from client
|
||||||
|
|
||||||
|
let query: localstate::queries_v16::Request =
|
||||||
|
match server.statequery().recv_while_acquired().await.unwrap() {
|
||||||
|
ClientQueryRequest::Query(q) => q.into_decode().unwrap(),
|
||||||
|
x => panic!("unexpected message from client: {x:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let addr_hex = "981D186018CE18F718FB185F188918A918C7186A186518AC18DD1874186D189E188410184D186F1882184D187D18C4184F1842187F18CA18A118DD";
|
||||||
|
let addr = hex::decode(addr_hex).unwrap();
|
||||||
|
let addr: Addr = addr.to_vec().into();
|
||||||
|
let addrs: Addrs = Vec::from([addr]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
localstate::queries_v16::Request::LedgerQuery(
|
||||||
|
localstate::queries_v16::LedgerQuery::BlockQuery(
|
||||||
|
5,
|
||||||
|
localstate::queries_v16::BlockQuery::GetUTxOByAddress(addrs),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(*server.statequery().state(), localstate::State::Querying);
|
||||||
|
|
||||||
|
let tx_hex = "1e4e5cf2889d52f1745b941090f04a65dea6ce56c5e5e66e69f65c8e36347c17";
|
||||||
|
let txbytes: [u8; 32] = hex::decode(tx_hex).unwrap().try_into().unwrap();
|
||||||
|
let transaction_id = Hash::from(txbytes);
|
||||||
|
let index = AnyUInt::MajorByte(2);
|
||||||
|
let lovelace = AnyUInt::MajorByte(2);
|
||||||
|
let hex_datum = "9118D81879189F18D81879189F1858181C18C918CF18711866181E185316189118BA";
|
||||||
|
let datum = hex::decode(hex_datum).unwrap().into();
|
||||||
|
let tag = TagWrap::<_, 24>::new(datum);
|
||||||
|
let inline_datum = Some((1_u16, tag));
|
||||||
|
let values = localstate::queries_v16::Values {
|
||||||
|
address: b"addr_test1vr80076l3x5uw6n94nwhgmv7ssgy6muzf47ugn6z0l92rhg2mgtu0"
|
||||||
|
.to_vec()
|
||||||
|
.into(),
|
||||||
|
amount: Value::Coin(lovelace),
|
||||||
|
inline_datum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let utxo = KeyValuePairs::from(vec![(
|
||||||
|
localstate::queries_v16::UTxO {
|
||||||
|
transaction_id,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
values,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let result = AnyCbor::from_encode(localstate::queries_v16::UTxOByAddress { utxo });
|
||||||
server.statequery().send_result(result).await.unwrap();
|
server.statequery().send_result(result).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(*server.statequery().state(), localstate::State::Acquired);
|
assert_eq!(*server.statequery().state(), localstate::State::Acquired);
|
||||||
|
|
@ -650,8 +704,56 @@ pub async fn local_state_query_server_and_client_happy_path() {
|
||||||
|
|
||||||
assert_eq!(result, localstate::queries_v16::StakeDistribution { pools });
|
assert_eq!(result, localstate::queries_v16::StakeDistribution { pools });
|
||||||
|
|
||||||
// client sends a ReAquire
|
let addr_hex = "981D186018CE18F718FB185F188918A918C7186A186518AC18DD1874186D189E188410184D186F1882184D187D18C4184F1842187F18CA18A118DD";
|
||||||
|
let addr = hex::decode(addr_hex).unwrap();
|
||||||
|
let addr: Addr = addr.to_vec().into();
|
||||||
|
let addrs: Addrs = Vec::from([addr]);
|
||||||
|
|
||||||
|
let request = AnyCbor::from_encode(localstate::queries_v16::Request::LedgerQuery(
|
||||||
|
localstate::queries_v16::LedgerQuery::BlockQuery(
|
||||||
|
5,
|
||||||
|
localstate::queries_v16::BlockQuery::GetUTxOByAddress(addrs),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
client.statequery().send_query(request).await.unwrap();
|
||||||
|
|
||||||
|
let result: localstate::queries_v16::UTxOByAddress = client
|
||||||
|
.statequery()
|
||||||
|
.recv_while_querying()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_decode()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tx_hex = "1e4e5cf2889d52f1745b941090f04a65dea6ce56c5e5e66e69f65c8e36347c17";
|
||||||
|
let txbytes: [u8; 32] = hex::decode(tx_hex).unwrap().try_into().unwrap();
|
||||||
|
let transaction_id = Hash::from(txbytes);
|
||||||
|
let index = AnyUInt::MajorByte(2);
|
||||||
|
let lovelace = AnyUInt::MajorByte(2);
|
||||||
|
let hex_datum = "9118D81879189F18D81879189F1858181C18C918CF18711866181E185316189118BA";
|
||||||
|
let datum = hex::decode(hex_datum).unwrap().into();
|
||||||
|
let tag = TagWrap::<_, 24>::new(datum);
|
||||||
|
let inline_datum = Some((1_u16, tag));
|
||||||
|
let values = localstate::queries_v16::Values {
|
||||||
|
address: b"addr_test1vr80076l3x5uw6n94nwhgmv7ssgy6muzf47ugn6z0l92rhg2mgtu0"
|
||||||
|
.to_vec()
|
||||||
|
.into(),
|
||||||
|
amount: Value::Coin(lovelace),
|
||||||
|
inline_datum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let utxo = KeyValuePairs::from(vec![(
|
||||||
|
localstate::queries_v16::UTxO {
|
||||||
|
transaction_id,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
values,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert_eq!(result, localstate::queries_v16::UTxOByAddress { utxo });
|
||||||
|
|
||||||
|
// client sends a ReAquire
|
||||||
client
|
client
|
||||||
.statequery()
|
.statequery()
|
||||||
.send_reacquire(Some(Point::Specific(1337, vec![1, 2, 3])))
|
.send_reacquire(Some(Point::Specific(1337, vec![1, 2, 3])))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue