Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannot decode branch: cannot decode child hash #3346

Closed
boern opened this issue Jun 21, 2023 · 8 comments · Fixed by #3295
Closed

cannot decode branch: cannot decode child hash #3346

boern opened this issue Jun 21, 2023 · 8 comments · Fixed by #3295
Assignees

Comments

@boern
Copy link

boern commented Jun 21, 2023

Describe the bug

I want to use gossamer trie to verify parachain header proofs that gets from polkadot rococo testnet,the code as follows:

	// data soure: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/explorer
	// number: 5,310,899
	// block hash: 0x6ba6142dd1848d98975b329219204f95382d50febefc3a2007c231d87a1efd93
	// block header stateRoot: 0x3b903e9947f26c4455f213b648661d0ef9b30018da7fa7be76bb5af2f5f75735

	stateRoot, err := hex.DecodeString("3b903e9947f26c4455f213b648661d0ef9b30018da7fa7be76bb5af2f5f75735")
	require.NoError(t, err)

	// module: paras
	// method: heads
	// encoded storage encodeStorageKey: 0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000
	encodeStorageKey, err := hex.DecodeString("cd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000")
	require.NoError(t, err)

	// para header
	// expectedParaHeader, err := hex.DecodeString("116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89")
	// require.NoError(t, err)

	proof1, err := hex.DecodeString("36ff6f7d467b87a9e803000021590f48b11891aee1f281f856256f37a20f8abc5d027434f89dd2decab922fe")
	require.NoError(t, err)
	n1, err := node.Decode(bytes.NewReader(proof1))
	require.NoError(t, err)
	_, _ = n1.CalculateMerkleValue()
	t.Logf("N1:%+v", n1)

	proof2, err := hex.DecodeString("800464801861be085002d2b0498ea992b13cfb1ca6b5e05a7ca54f6180dcc1bcd10a9f0680f6f6801e4b41e2e6d8ec194dba122bfb9eb33feb2545ef5144cea79551f7cc5280f6370779a48f025599265f348f955ee0b12eeb99238950c07f5562091f2186d48043e819b824d89dc6e744b5342c963829d44a93a1bdad2405615856f67945c9e0")
	require.NoError(t, err)
	n2, err := node.Decode(bytes.NewReader(proof2))
	require.NoError(t, err)
	_, _ = n2.CalculateMerkleValue()
	t.Log("N2:", n2)

	proof3, err := hex.DecodeString("80cf93807b212eaf64882b542230cc1fa87d9505181a516c0dfd67ac55d3158cd753f8ad80862c9aecf51563f0b4f54f6d2a325bec9afdf62a66f595e150203fd9a144b1e580c2fb34bc8b88011ab509fd52c25b3469bbc9353f472a05decd83449af1e3677d80ecbe9453c51b405848014efabda8a0cde4b9458e7c26a4d9eeb589c52bdb5eb1809c43b10cb7509edfd059982f30f20ba7368bbd82786184cf0a5be813cd07490a8088d755e63972295bd4772b7322e27adb3358090fc2f16c66e65341de0d9bd22980891eac33e3ee82a64283ab12370710911e866576869040634657bcc78a1385a180a4c9385e359a9977574174c4d31beab6206a569ad15ef435bf784f16623e1d21802e89324e6a5e0be929b37bb44bdbf6619e6af80cbdebc9bd67a44c8aa072ef3980d8eabbfa85a6309a2ff6dac1a06e6a6d214faba34887e6f8e14c0d0d1858711e")
	require.NoError(t, err)
	n3, err := node.Decode(bytes.NewReader(proof3))
	require.NoError(t, err)
	_, _ = n3.CalculateMerkleValue()
	t.Log("N3:", n3)

	proof4, err := hex.DecodeString("80ffff80fe86a6cb12b2233729f7834ff614d56c968207d9ae09266cd3835e32fc7bbdba80ee3fa56aef90d79d5a7c8e5f6e85252288631533a5a8ebf405846bdc3cedcaf38019c7f105c5c4278d8f4b5a67adb644c0d4b056b6affbe8721df8ce54865e8fe8800b223e5d94298635855f517e49a2e925d4e39de3e27ff1af06b658de5a2e8280804165dde7158903211dc880ebc441e6fc3ef9f8a1c5f99fd261178c4e97206805808dcd7a042792b39ad7fcbd97e77273b3d0b250c3203398f7290a7d3e0d7cc20c807c3fac76e1315865f8e8fcda6748711a415fc87187794acba9584b2c151b956080091b6db629efcf3857b17a2df2fa8b296c5aedc80db088f4e6a560053c7ce890803e5026745b944d7fa9a39c6f08292b88efbaec1e1041ccd78348053881c1bf86800451959b47e46ecdb0edd2df37445db0a629898058bae12a73ad88379a130fe080f52d9d5dfa99ec1762f86ca9b229e11e8f7910633e1032ece9c89e28892397a4805e6def858a456048697cbdb9af83fc447c671b0cf283f1409400f3a6c506321f80f8093e29566bd8ebec39521f87c10156d1c424a767aadecd231adf5c55f5f538806075e1c36fcba711bb56da63b85b31fdf481041a0acb93b035a6ebb9987734f4808f0cfeb4e4785d0fcb162883963e55e8cbc49c2398f1f275cfe5d2484bac2f9780b063dd4a5cd6b883c54ff93751d114e20e99e2e05eff766ddcca317b67d4f08a")
	require.NoError(t, err)
	n4, err := node.Decode(bytes.NewReader(proof4))
	require.NoError(t, err)
	_, _ = n4.CalculateMerkleValue()
	t.Log("N4:", n4)

	proof5, err := hex.DecodeString("9e710b30bd2eab0352ddcc26417aa1945fcb801998fc2315e4329c3d3c59ff787fef52f1707abcf997f8114a016594b6716ce8803a5b05f6d48162e04748dce0050d00025c0d51a4845ea2119f66952522b2cd2b80549fd5090d980b3ae9b1b61196d5f617c57b2f4e5eb5f2e51e4c5c857429363180196a38280fc3af7f724552363e4833e604127b44cb46271dd28151765bb91cf0505f0e7b9012096b41c4eb3aaf947f6ea4290800004c5f03c716fb8fff3de61a883bb76adb34a2040080ae1c868ee54941861f121640db72e895211b6748da302cc4ddf39f715c76e7528052e248e38ba2e7f604c09c090bfb8abc6bc68c2f92f34f454606b4a63102e58a8026a2dd112b0ca67351d4abb723dc41978d5865dd4b208b37eed5200bbc0ba0f4807bd51e23ee41e85d99c3985aa8b0f859f70f20fa783b7055b5161adfc69e2d5180a6a96fae992961a174ea36d7e23e69c08d45ecacc82e14fbc3546b8d60ceae48")
	require.NoError(t, err)
	n5, err := node.Decode(bytes.NewReader(proof5))
	require.NoError(t, err)
	_, _ = n5.CalculateMerkleValue()
	t.Log("N5:", n5)

	proof6, err := hex.DecodeString("9f0b3c252fcb29d88eff4f3de5de4476c3ffff8076bed1a9045e1937ab7ad7cff6e667c66351022b28103771310fa09e0a07708f808ff91cb4e274aa25177bbea2d77d5693f3da34820ecb82d6a06529de8bc0beb580b51c90d98a3cc501566107ce9b89e91609de184f72c521efe2e2486beb095dc280af5427f678c5055f4039369c53aaa785a3767ba10cf2e42b5cc9b625d8021bca8044ea5b04397b504579d34a01419b6f0fb0c4f3003b3e6e0b99687cc88f398670803d46b9972edc81cd44df296d227eafde0abd880a53ea37632ddb558e913315e68010a7cfabb7bf234b6efd0fba3d30758e762ec52d14d329e0b9ebd5c84ec7752680c9b8e0f77483284d53f3ccb7ca3a217faa9b50a819cfa557438cfa9813306910809bb471046c73d5edf5683b4e3408714f428ecdf8c447e80f8335b4049e555c2e80fd2a0bea95ba513ddd672bdd9d9fbfd1c9588731d06e9afa5004332250054ea180263061f7d953b0fba1d98b9c6529ce6c1d78af0012180caccb388c4921216de1803543b7e854863de08e6ce77ac171ecec6b64d419e58d6171fe654ee279b5f8c28061cd0a2e641fbce3fd78bad7f2b298918a187aca625491c1a1898763705840fa807aa5071686a8d5d83f8db3531aaaa181ea3843746bfc7917193b1dfcfbb0c49b8065ad311a5eb95c25f400fd199f1005a4ba6f62a7049117e9466dba91c1df949d80aa704996ec32908132b67245030b4d8456c46415837150ef58898df6b9b0ce5e")
	require.NoError(t, err)
	n6, err := node.Decode(bytes.NewReader(proof6))
	require.NoError(t, err)
	_, _ = n6.CalculateMerkleValue()
	t.Log("N6:", n6)

	proof7, err := hex.DecodeString("e902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89")
	require.NoError(t, err)
	n7, err := node.Decode(bytes.NewReader(proof7))
	require.NoError(t, err)
	_, _ = n7.CalculateMerkleValue()
	t.Log("N7:", n7)

	proof := [][]byte{proof1, proof2, proof3, proof4, proof5, proof6, proof7}

	trie, err := buildTrie(proof, stateRoot)
	require.NoError(t, err)
	value := trie.Get(encodeStorageKey)
	t.Log("The Key Value:", value)

Expected Behavior

Use the proofs from polkadot rococo testnet to build trie tree and get the parachain header value by the storage key

Current Behavior

Error: Received unexpected error:
cannot decode branch: cannot decode child hash: at index 2: unknown prefix for compact uint: 59

I write a sample that uses the substrate trie to verify the same proofs, the result is ok, Isn't gossamer trie unable to verify the proof from polkadot?

The complete test code link as follows:
golang testing code: https://github.com/boern/gossamer/blob/diego/trieV1/new-headers/lib/trie/proof/state_proof_test.go#L75
rust testing code: https://github.com/boern/trie-test/blob/main/rust/src/state_proof_example.rs#L71

  • go version: 1.19.7
  • gossamer branch: diego/trieV1/new-headers
  • operating system: ubuntu 20.04
@dimartiro
Copy link
Contributor

dimartiro commented Jun 26, 2023

Hi @boern I've been investigating and these are my finding

Error decoding node

We are failing decoding the last node of the proof because it has an error in the 3rd child data length

cannot decode branch: cannot decode child hash: at index 2: unknown prefix for compact uint: 59

But I'm sure this is not a gossamer error, I tested it in Substrate and Smoldot and they have similar issues

Smoldot test example

Using smoldot = "0.5.0"

use hex_literal::hex;
use smoldot::trie::{proof_decode::decode_and_verify_proof, proof_node_codec::decode};

pub async fn decode_failing_node_using_smoldot() -> Result<(), smoldot::trie::proof_node_codec::Error> {
    let data = hex!("e902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89");
    let decoded = decode(&data)?;

    println!("Decoded:\n{:?}", decoded);

    Ok(())
}

It fails with:

Error: ChildLenDecode

Substrate test example

Using trie-db = "0.27.1"

use hex_literal::hex;
use trie_db::{node::OwnedNode, NibbleSlice};

pub async fn decode_failing_node_using_substrate() -> Result<(), Box<dyn std::error::Error>> {
    let data = hex!("e902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89");
    let node = OwnedNode::new::<NodeCodec<sp_runtime::traits::BlakeTwo256>>(data)?;

    println!("Trie:\n{:?}", node);

    Ok(())
}

Getting this error:

Error: Decode(Error { cause: None, desc: "out of range decoding Compact<u32>" })

So we are consistent with Substrate and Smoldot behavior.

Open questions

  • How did you got the proof data?
  • Can we check if there is something missing?

Regarding the get key value method

If you change your gossamer test to not validate the nodes it will build the trie and get the key (as substrate does) but the key is different in substrate and gossamer

Substrate result:

[233, 2, 17, 106, 40, 17, 234, 170, 55, 47, 205, 140, 118, 155, 95, 67, 61, 57, 149, 135, 43, 33, 196, 104, 220, 252, 98, 112, 225, 249, 250, 7, 22, 126, 170, 76, 124, 0, 245, 249, 129, 192, 180, 218, 254, 60, 16, 41, 231, 15, 178, 144, 41, 79, 194, 31, 4, 1, 151, 172, 0, 242, 9, 219, 246, 89, 169, 123, 216, 63, 125, 195, 252, 66, 233, 133, 144, 94, 162, 49, 59, 37, 81, 183, 38, 146, 81, 14, 151, 68, 73, 59, 213, 37, 5, 94, 39, 226, 149, 148, 138, 17, 8, 6, 97, 117, 114, 97, 32, 146, 213, 92, 8, 0, 0, 0, 0, 5, 97, 117, 114, 97, 1, 1, 22, 253, 79, 237, 184, 236, 216, 235, 160, 217, 7, 183, 189, 83, 75, 38, 11, 192, 184, 106, 14, 154, 31, 216, 241, 140, 184, 94, 144, 115, 244, 66, 166, 169, 245, 70, 11, 251, 36, 67, 188, 230, 123, 143, 219, 161, 123, 189, 41, 39, 189, 173, 143, 198, 174, 2, 28, 3, 226, 200, 179, 227, 62, 137]

Gossamer result:

[33 89 15 72 177 24 145 174 225 242 129 248 86 37 111 55 162 15 138 188 93 2 116 52 248 157 210 222 202 185 34 254]

New test:

func TestParachainHeaderStateProof(t *testing.T) {
	stateRoot, err := hex.DecodeString("3b903e9947f26c4455f213b648661d0ef9b30018da7fa7be76bb5af2f5f75735")
	require.NoError(t, err)

	encodeStorageKey, err := hex.DecodeString("cd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000")
	require.NoError(t, err)

	proof1, err := hex.DecodeString("36ff6f7d467b87a9e803000021590f48b11891aee1f281f856256f37a20f8abc5d027434f89dd2decab922fe")
	require.NoError(t, err)

	proof2, err := hex.DecodeString("800464801861be085002d2b0498ea992b13cfb1ca6b5e05a7ca54f6180dcc1bcd10a9f0680f6f6801e4b41e2e6d8ec194dba122bfb9eb33feb2545ef5144cea79551f7cc5280f6370779a48f025599265f348f955ee0b12eeb99238950c07f5562091f2186d48043e819b824d89dc6e744b5342c963829d44a93a1bdad2405615856f67945c9e0")
	require.NoError(t, err)

	proof3, err := hex.DecodeString("80cf93807b212eaf64882b542230cc1fa87d9505181a516c0dfd67ac55d3158cd753f8ad80862c9aecf51563f0b4f54f6d2a325bec9afdf62a66f595e150203fd9a144b1e580c2fb34bc8b88011ab509fd52c25b3469bbc9353f472a05decd83449af1e3677d80ecbe9453c51b405848014efabda8a0cde4b9458e7c26a4d9eeb589c52bdb5eb1809c43b10cb7509edfd059982f30f20ba7368bbd82786184cf0a5be813cd07490a8088d755e63972295bd4772b7322e27adb3358090fc2f16c66e65341de0d9bd22980891eac33e3ee82a64283ab12370710911e866576869040634657bcc78a1385a180a4c9385e359a9977574174c4d31beab6206a569ad15ef435bf784f16623e1d21802e89324e6a5e0be929b37bb44bdbf6619e6af80cbdebc9bd67a44c8aa072ef3980d8eabbfa85a6309a2ff6dac1a06e6a6d214faba34887e6f8e14c0d0d1858711e")
	require.NoError(t, err)

	proof4, err := hex.DecodeString("80ffff80fe86a6cb12b2233729f7834ff614d56c968207d9ae09266cd3835e32fc7bbdba80ee3fa56aef90d79d5a7c8e5f6e85252288631533a5a8ebf405846bdc3cedcaf38019c7f105c5c4278d8f4b5a67adb644c0d4b056b6affbe8721df8ce54865e8fe8800b223e5d94298635855f517e49a2e925d4e39de3e27ff1af06b658de5a2e8280804165dde7158903211dc880ebc441e6fc3ef9f8a1c5f99fd261178c4e97206805808dcd7a042792b39ad7fcbd97e77273b3d0b250c3203398f7290a7d3e0d7cc20c807c3fac76e1315865f8e8fcda6748711a415fc87187794acba9584b2c151b956080091b6db629efcf3857b17a2df2fa8b296c5aedc80db088f4e6a560053c7ce890803e5026745b944d7fa9a39c6f08292b88efbaec1e1041ccd78348053881c1bf86800451959b47e46ecdb0edd2df37445db0a629898058bae12a73ad88379a130fe080f52d9d5dfa99ec1762f86ca9b229e11e8f7910633e1032ece9c89e28892397a4805e6def858a456048697cbdb9af83fc447c671b0cf283f1409400f3a6c506321f80f8093e29566bd8ebec39521f87c10156d1c424a767aadecd231adf5c55f5f538806075e1c36fcba711bb56da63b85b31fdf481041a0acb93b035a6ebb9987734f4808f0cfeb4e4785d0fcb162883963e55e8cbc49c2398f1f275cfe5d2484bac2f9780b063dd4a5cd6b883c54ff93751d114e20e99e2e05eff766ddcca317b67d4f08a")
	require.NoError(t, err)

	proof5, err := hex.DecodeString("9e710b30bd2eab0352ddcc26417aa1945fcb801998fc2315e4329c3d3c59ff787fef52f1707abcf997f8114a016594b6716ce8803a5b05f6d48162e04748dce0050d00025c0d51a4845ea2119f66952522b2cd2b80549fd5090d980b3ae9b1b61196d5f617c57b2f4e5eb5f2e51e4c5c857429363180196a38280fc3af7f724552363e4833e604127b44cb46271dd28151765bb91cf0505f0e7b9012096b41c4eb3aaf947f6ea4290800004c5f03c716fb8fff3de61a883bb76adb34a2040080ae1c868ee54941861f121640db72e895211b6748da302cc4ddf39f715c76e7528052e248e38ba2e7f604c09c090bfb8abc6bc68c2f92f34f454606b4a63102e58a8026a2dd112b0ca67351d4abb723dc41978d5865dd4b208b37eed5200bbc0ba0f4807bd51e23ee41e85d99c3985aa8b0f859f70f20fa783b7055b5161adfc69e2d5180a6a96fae992961a174ea36d7e23e69c08d45ecacc82e14fbc3546b8d60ceae48")
	require.NoError(t, err)

	proof6, err := hex.DecodeString("9f0b3c252fcb29d88eff4f3de5de4476c3ffff8076bed1a9045e1937ab7ad7cff6e667c66351022b28103771310fa09e0a07708f808ff91cb4e274aa25177bbea2d77d5693f3da34820ecb82d6a06529de8bc0beb580b51c90d98a3cc501566107ce9b89e91609de184f72c521efe2e2486beb095dc280af5427f678c5055f4039369c53aaa785a3767ba10cf2e42b5cc9b625d8021bca8044ea5b04397b504579d34a01419b6f0fb0c4f3003b3e6e0b99687cc88f398670803d46b9972edc81cd44df296d227eafde0abd880a53ea37632ddb558e913315e68010a7cfabb7bf234b6efd0fba3d30758e762ec52d14d329e0b9ebd5c84ec7752680c9b8e0f77483284d53f3ccb7ca3a217faa9b50a819cfa557438cfa9813306910809bb471046c73d5edf5683b4e3408714f428ecdf8c447e80f8335b4049e555c2e80fd2a0bea95ba513ddd672bdd9d9fbfd1c9588731d06e9afa5004332250054ea180263061f7d953b0fba1d98b9c6529ce6c1d78af0012180caccb388c4921216de1803543b7e854863de08e6ce77ac171ecec6b64d419e58d6171fe654ee279b5f8c28061cd0a2e641fbce3fd78bad7f2b298918a187aca625491c1a1898763705840fa807aa5071686a8d5d83f8db3531aaaa181ea3843746bfc7917193b1dfcfbb0c49b8065ad311a5eb95c25f400fd199f1005a4ba6f62a7049117e9466dba91c1df949d80aa704996ec32908132b67245030b4d8456c46415837150ef58898df6b9b0ce5e")
	require.NoError(t, err)

	proof7, err := hex.DecodeString("e902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89")
	require.NoError(t, err)

	proof := [][]byte{proof1, proof2, proof3, proof4, proof5, proof6, proof7}

	trie, err := buildTrie(proof, stateRoot)
	require.NoError(t, err)
	value := trie.Get(encodeStorageKey)
	t.Log("The Key Value:", value)

	//Value obtained by running the same test in rust
	expected := []uint{233, 2, 17, 106, 40, 17, 234, 170, 55, 47, 205, 140, 118, 155, 95, 67, 61, 57, 149, 135, 43, 33, 196, 104, 220, 252, 98, 112, 225, 249, 250, 7, 22, 126, 170, 76, 124, 0, 245, 249, 129, 192, 180, 218, 254, 60, 16, 41, 231, 15, 178, 144, 41, 79, 194, 31, 4, 1, 151, 172, 0, 242, 9, 219, 246, 89, 169, 123, 216, 63, 125, 195, 252, 66, 233, 133, 144, 94, 162, 49, 59, 37, 81, 183, 38, 146, 81, 14, 151, 68, 73, 59, 213, 37, 5, 94, 39, 226, 149, 148, 138, 17, 8, 6, 97, 117, 114, 97, 32, 146, 213, 92, 8, 0, 0, 0, 0, 5, 97, 117, 114, 97, 1, 1, 22, 253, 79, 237, 184, 236, 216, 235, 160, 217, 7, 183, 189, 83, 75, 38, 11, 192, 184, 106, 14, 154, 31, 216, 241, 140, 184, 94, 144, 115, 244, 66, 166, 169, 245, 70, 11, 251, 36, 67, 188, 230, 123, 143, 219, 161, 123, 189, 41, 39, 189, 173, 143, 198, 174, 2, 28, 3, 226, 200, 179, 227, 62, 137}
	require.Equal(t, expected, value)
}

Open questions

  • What happen in smoldot? (I couldn't test it yet)

Next steps:

  • I'll be debugging it this week to understand more about the get value method

Let me know if you discover anything else about it

@dimartiro dimartiro self-assigned this Jun 27, 2023
@boern
Copy link
Author

boern commented Jul 1, 2023

@dimartiro thx for your reply !
In my test case, I want to prove in the light client that the header of the parachain is included in the relay chain, and the proof data is obtained in the following way:
First, through polkadotjs -> chain state, query the storage key and encoded head bytes of the parachain header
https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/chainstate
image

Then, by polkadotjs -> rpc calls, get the state proof of the parachain header
https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frococo-rpc.polkadot.io#/rpc
image

Finally, use the above proof data to construct a trie db, get the value, and compare whether the value is equal to the encoded head bytes.

You can repeat the above steps to get the latest proof data and then construct the test !

Additional Information:
parachain name: Picasso Testnet
parachian id : 2087
or you can use other parachain info, eg: astar

@dimartiro
Copy link
Contributor

dimartiro commented Jul 3, 2023

@boern thank you for your reply, and all the details.

TL;DR: I've been investigating and the difference between Substrate and our implementation is related with the DB they are building and the way they are retrieving the key for a hashed value. I fixed it in my current PR

Overview

image

https://spec.polkadot.network/chap-state#defn-merkle-value

Every node larger than 32 bytes is hashed to reduce the space usage, so given this and reading the substrate implementation I deduced that if you want to retrieve the value for a hashed node you will need to keep a mapping between the hashed nodes and the real values.

What is happening?

We are returning this value:

[33 89 15 72 177 24 145 174 225 242 129 248 86 37 111 55 162 15 138 188 93 2 116 52 248 157 210 222 202 185 34 254]

If you print the substrate trie you will see that this value is the same as what we are returning

image

But if you print the value substrate is returning is different

let value = trie.get_with(&encode_storage_key, |v: &[u8]| v.to_vec()).unwrap().unwrap();
[233, 2, 17, 106, 40, 17, 234, 170, 55, 47, 205, 140, 118, 155, 95, 67, 61, 57, 149, 135, 43, 33, 196, 104, 220, 252, 98, 112, 225, 249, 250, 7, 22, 126, 170, 76, 124, 0, 245, 249, 129, 192, 180, 218, 254, 60, 16, 41, 231, 15, 178, 144, 41, 79, 194, 31, 4, 1, 151, 172, 0, 242, 9, 219, 246, 89, 169, 123, 216, 63, 125, 195, 252, 66, 233, 133, 144, 94, 162, 49, 59, 37, 81, 183, 38, 146, 81, 14, 151, 68, 73, 59, 213, 37, 5, 94, 39, 226, 149, 148, 138, 17, 8, 6, 97, 117, 114, 97, 32, 146, 213, 92, 8, 0, 0, 0, 0, 5, 97, 117, 114, 97, 1, 1, 22, 253, 79, 237, 184, 236, 216, 235, 160, 217, 7, 183, 189, 83, 75, 38, 11, 192, 184, 106, 14, 154, 31, 216, 241, 140, 184, 94, 144, 115, 244, 66, 166, 169, 245, 70, 11, 251, 36, 67, 188, 230, 123, 143, 219, 161, 123, 189, 41, 39, 189, 173, 143, 198, 174, 2, 28, 3, 226, 200, 179, 227, 62, 137]

Actually, that value is the byte representation for the last proof:

println!("The Key Value:\n{:?}", hex::encode(value));
0xe902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89

So, why is Substrate returning a different value when you are trying to retrieve that key?

The difference is in the way substrate is handling the hashed values.

In substrate you have to build a DB to use it later to build the trie

let db = StorageProof::new(proof).into_memory_db::<sp_runtime::traits::BlakeTwo256>();

This DB is a key value store that has hashed nodes as keys and "real" nodes as value
Then, when you try to retrieve a value executing this line:

let value = trie.get_with(&encode_storage_key, |v: &[u8]| v.to_vec()).unwrap().unwrap();

Internally it executes this lookup

fn load_value(
		v: Value,
		prefix: Prefix,
		full_key: &[u8],
		db: &dyn HashDBRef<L::Hash, DBValue>,
		recorder: &mut Option<&mut dyn TrieRecorder<TrieHash<L>>>,
		query: Q,
	) -> Result<Q::Item, TrieHash<L>, CError<L>> {
		match v {
			Value::Inline(value) => Ok(query.decode(&value)),
			Value::Node(hash) => {
				let mut res = TrieHash::<L>::default();
				res.as_mut().copy_from_slice(hash);
				if let Some(value) = db.get(&res, prefix) {
					if let Some(recorder) = recorder {
						recorder.record(TrieAccess::Value {
							hash: res,
							value: value.as_slice().into(),
							full_key,
						});
					}

					Ok(query.decode(&value))
				} else {
					Err(Box::new(TrieError::IncompleteDatabase(res)))
				}
			},
		}
	}

Getting the node from the DB using the key (that is the node value) for not inlined nodes

...
Value::Node(hash) => {
	let mut res = TrieHash::<L>::default();
	res.as_mut().copy_from_slice(hash);
	if let Some(value) = db.get(&res, prefix)
        ...

We, on the other hand, are returning the storage value directly from the hashed nodes (so we are returning the hashed value)

Solution

I pushed this commit with a similar solution than subtrate in my current PR. Now we are mapping the hashed nodes with their corresponding "real" nodes and we are returning the values from that mapping in case storage value is a hashed value.

So the test pass now.

image

PS: If you want to verify a proof for a given value you can use the Verify method as I did in my test so will be simpler for you since you don't have to build the trie.

Let me know if it works for you, I'll be adding this issue in that PR to close it when it's merged

@boern
Copy link
Author

boern commented Jul 5, 2023

@dimartiro thank you, I synced your code and tested it, it works, but the node decoding still has errors, did you find the cause of this problem?

n7, err := node.Decode(bytes.NewReader(proof7))
require.NoError(t, err)
_, _ = n7.CalculateMerkleValue()
t.Log("N7:", n7)

@dimartiro
Copy link
Contributor

dimartiro commented Jul 5, 2023

@boern thanks for the confirmation.

Do you really need to decode it? Did you try with Substrate?

Based on my tests in my previous comment, that node failed to be decoded in Gossamer, Substrate and Smoldot because it has an invalid child length.

Error: Decode(Error { cause: None, desc: "out of range decoding Compact<u32>" })

Let me know if you can decode it in substrate and the method you are using.

@boern
Copy link
Author

boern commented Jul 6, 2023

#[tokio::test]
pub async fn decode_node_using_substrate() -> Result<(), Box<dyn std::error::Error>> {
    let data = hex!("e902116a2811eaaa372fcd8c769b5f433d3995872b21c468dcfc6270e1f9fa07167eaa4c7c00f5f981c0b4dafe3c1029e70fb290294fc21f040197ac00f209dbf659a97bd83f7dc3fc42e985905ea2313b2551b72692510e9744493bd525055e27e295948a110806617572612092d55c08000000000561757261010116fd4fedb8ecd8eba0d907b7bd534b260bc0b86a0e9a1fd8f18cb85e9073f442a6a9f5460bfb2443bce67b8fdba17bbd2927bdad8fc6ae021c03e2c8b3e33e89");
    let node = OwnedNode::new::<NodeCodec<sp_runtime::traits::BlakeTwo256>>(data)?;

    println!("Trie:\n{:?}", node);

    Ok(())
}

test reult:

Error: Decode(Error { cause: None, desc: "out of range decoding Compact<u32>" })

Same result as your test, as I understand it, the last node should also be decoded!
This issue does not affect my program, just want to know the cause of the error, I think this issue can be closed, thank you very much! @dimartiro

@dimartiro
Copy link
Contributor

@boern I have the same question, could be related with a bug probably.
Since substrate is not decoding all nodes when you query for a value this could go unnoticed.

Glad to see that the change is working for you, please let us know if you have any other issue.
This issue will be closed when my current PR is merged.

Copy link

🎉 This issue has been resolved in version 0.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants