getting Unsupported Operation for transfers
Closed this issue · 4 comments
I'm getting the following error (from node logs):
2023-11-28T19:06:08.554860Z WARN consensus: iroha_core::block::pending: Transaction validation failed
reason=Validation failed caused_by=Some(NotPermitted("TransferExpr { source_id:
AssetId(energy##matias@cognition), object: 12_u32, destination_id: AssetId(energy##pepe@cognition) }:
Unsupported operation"))
This is my dev setup:
Node
: stable-2.0.0-pre-rc.20iroha2/client
: 7.0.0iroha2/data-model
: 7.0.0
This is my code:
const transfer = async (from, to, asset, quantity) => {
logger.info(`[srv] transfer ${from} to ${to} ${quantity} of ${asset} ...`);
const [senderAccName, senderDomainId] = from.split('@');
const senderAccId = sugar.accountId(senderAccName, senderDomainId);
const [receiverAccName, receiverDomainId] = to.split('@');
const receiverAccId = sugar.accountId(receiverAccName, receiverDomainId);
const [assetName, assetDomainId] = asset.split('#');
const assetDefId = sugar.assetDefinitionId(assetName, assetDomainId);
const { IdBox } = datamodel;
const { assetId } = sugar;
const senderIdBox = IdBox('AssetId', assetId(senderAccId, assetDefId));
const receiverIdBox = IdBox('AssetId', assetId(receiverAccId, assetDefId));
const value = sugar.value.numericU32(quantity);
const transferAsset = sugar.instruction.transfer(
senderIdBox,
value,
receiverIdBox
);
const instr = sugar.executable.instructions(transferAsset);
const { pre, client } = _clientFactory('matias', 'cognition');
try {
const data = await client.submitExecutable(pre, instr);
logger.info('[srv] transfer completed ...');
return { data };
} catch (error) {
console.log(error);
return { error: error.message };
}
};
Additional information:
- I get the same result without using the sugar package.
- I can transfer with no problem using CLI, with the same config file, to the same node running on a local Docker instance.
The node is created from the following Dockerfile:
# Use the official hyperledger/iroha2 base image with the specified version
FROM hyperledger/iroha2:stable-2.0.0-pre-rc.20
# Set environment variables
ENV TORII_P2P_ADDR 0.0.0.0:1337
ENV TORII_API_URL 0.0.0.0:8080
ENV TORII_TELEMETRY_URL 0.0.0.0:8180
ENV IROHA_PUBLIC_KEY "ed01201C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B"
ENV IROHA_PRIVATE_KEY '{"digest_function": "ed25519", "payload": "282ED9F3CF92811C3818DBC4AE594ED59DC1A2F78E4241E31924E101D6B1FB831C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B"}'
ENV SUMERAGI_TRUSTED_PEERS '[{"address":"0.0.0.0:1337", "public_key": "ed01201C61FAF8FE94E253B93114240394F79A607B7FA55F9E5A41EBEC74B88055768B"}]'
ENV IROHA_GENESIS_ACCOUNT_PUBLIC_KEY "ed01203F4E3E98571B55514EDC5CCF7E53CA7509D89B2868E62921180A6F57C2F4E255"
ENV IROHA_GENESIS_ACCOUNT_PRIVATE_KEY '{"digest_function": "ed25519", "payload": "038AE16B219DA35AA036335ED0A43C28A2CC737150112C78A7B8034B9D99C9023F4E3E98571B55514EDC5CCF7E53CA7509D89B2868E62921180A6F57C2F4E255"}'
ENV IROHA_GENESIS_WAIT_FOR_PEERS_RETRY_COUNT_LIMIT 100
ENV IROHA_GENESIS_WAIT_FOR_PEERS_RETRY_PERIOD_MS 500
ENV IROHA_GENESIS_GENESIS_SUBMISSION_DELAY_MS 1000
# Expose the required ports
EXPOSE 1337 8080 8180
# Set the working directory
WORKDIR /opt/iroha_data
# Copy the configuration files
COPY ./configs/peer/stable /config
# Command to run on container startup
CMD ["iroha", "--submit-genesis"]
And executed like this:
docker build -t local-iroha .
docker run -d -p 8080:8080 -p 1337:1337 -p 8180:8100 local-iroha
@a1salimbene, please add:
- the commit on the stable branch (
git rev-parse --short HEAD
output) - example code without the
sugar
class, as it adds an unpredictable factor we can not test yet
@6r1d information request below:
> git rev-parse --short HEAD
7acb8a44
> git branch
iroha2-dev
iroha2-lts
* iroha2-stable
Here's the code without using the sugar
class. I'm getting exact same err:
const transfer = async (from, to, asset, quantity) => {
logger.info(`[srv] transfer ${from} to ${to} ${quantity} of ${asset} ...`);
const [senderAccName, senderDomainId] = from.split('@');
const [receiverAccName, receiverDomainId] = to.split('@');
const [assetName, assetDomainId] = asset.split('#');
const {
AccountId,
AssetDefinitionId,
AssetId,
DomainId,
InstructionExpr,
Expression,
Executable,
IdBox,
NumericValue,
TransferExpr,
Value,
VecInstructionExpr,
} = datamodel;
const senderDomain = DomainId({
name: senderDomainId,
});
const receiverDomain = DomainId({
name: receiverDomainId,
});
const assetDomain = DomainId({
name: assetDomainId,
});
const assetDefinitionId = AssetDefinitionId({
name: assetName,
domain_id: assetDomain,
});
const fromAccount = AccountId({
name: senderAccName,
domain_id: senderDomain,
});
const toAccount = AccountId({
name: receiverAccName,
domain_id: receiverDomain,
});
const amountToTransfer = Value('Numeric', NumericValue('U32', quantity));
const evaluatesToAssetId = (assetId) =>
Expression('Raw', Value('Id', IdBox('AssetId', assetId)));
const transferAssetInstruction = TransferExpr({
source_id: evaluatesToAssetId(
AssetId({
definition_id: assetDefinitionId,
account_id: fromAccount,
})
),
destination_id: evaluatesToAssetId(
AssetId({
definition_id: assetDefinitionId,
account_id: toAccount,
})
),
object: Expression('Raw', amountToTransfer),
});
const instr = InstructionExpr('Transfer', transferAssetInstruction);
const exec = Executable('Instructions', VecInstructionExpr([instr]));
const { pre, client } = _clientFactory('matias', 'cognition');
try {
const data = await client.submitExecutable(pre, exec);
logger.info('[srv] transfer completed ...');
return { data };
} catch (error) {
console.log(error);
return { error: error.message };
}
};
I found the problem looking at a rust test, see below:
fn transfer_isi_should_be_valid() {
let _instruction = TransferExpr::new(
IdBox::AssetId("btc##seller@crypto".parse().expect("Valid")),
12_u32,
IdBox::AccountId("buyer@crypto".parse().expect("Valid")),
);
}
The third param is an AccountId
(destination target), but the first param is an AssetId
(which already includes the source AccountId
.
Thus, this works:
const toAccount = sugar.accountId(receiverAccName, receiverDomainId);
const transferIsi = sugar.instruction.transfer(
datamodel.IdBox('AssetId', sugar.assetId(fromAccount, assetDefinitionId)),
amountToTransfer,
datamodel.IdBox('AccountId', toAccount)
);
but this doesn't
const transferAsset = sugar.instruction.transfer(
datamodel.IdBox('AssetId', sugar.assetId(senderAccId, assetDefId)),
amountToTransfer,
datamodel.IdBox('AssetId', sugar.assetId(receiverAccId, assetDefId))
);
Is worth mentioning that the test case from the JS code appears to be wrong then. In all js samples I found, source and destination IDs are AssetId
. For instance, this is from the JS docs:
const transferAssetInstruction = Instruction(
'Transfer',
TransferBox({
source_id: evaluatesToAssetId(
AssetId({
definition_id: assetDefinitionId,
account_id: fromAccount,
}),
),
destination_id: evaluatesToAssetId(
AssetId({
definition_id: assetDefinitionId,
account_id: toAccount,
}),
),
object: EvaluatesToValue({
expression: Expression('Raw', amountToTransfer),
}),
}),
)
However, this follows the logic from the cli that implements "from" → "to" → "asset", instead of "asset" → "to" like Rust:
iroha_client_cli asset transfer --from matias@cognition --to pepe@cognition --asset-id energy#cognition --quantity 5
So it's all a bit confusing but hopefully, it helps. Now I don't think there's a bug or anything like it. I think this is a case of outdated documentation or lack thereof.
Thank you. I'm closing this issue and will raise the topic with our documentation team.