diff --git a/lib/runtime/wazero/imports.go b/lib/runtime/wazero/imports.go index c6e5d6378c..000d9b9985 100644 --- a/lib/runtime/wazero/imports.go +++ b/lib/runtime/wazero/imports.go @@ -32,7 +32,8 @@ var ( log.AddContext("module", "wazero"), ) - noneEncoded []byte = []byte{0x00} + noneEncoded []byte = []byte{0x00} + emptyByteVectorEncoded []byte = scale.MustMarshal([]byte{}) ) const ( @@ -816,6 +817,61 @@ func ext_trie_blake2_256_root_version_1(ctx context.Context, m api.Module, dataS return ptr } +func ext_trie_blake2_256_root_version_2(ctx context.Context, m api.Module, dataSpan uint64, version uint32) uint32 { + rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) + if rtCtx == nil { + panic("nil runtime context") + } + + stateVersionBytes, _ := m.Memory().Read(version, 4) + stateVersion, err := trie.ParseVersion(binary.LittleEndian.Uint32(stateVersionBytes)) + if err != nil { + logger.Errorf("failed parsing state version: %s", err) + return 0 + } + + data := read(m, dataSpan) + + t := trie.NewEmptyTrie() + + type kv struct { + Key, Value []byte + } + + // this function is expecting an array of (key, value) tuples + var kvs []kv + if err := scale.Unmarshal(data, &kvs); err != nil { + logger.Errorf("failed scale decoding data: %s", err) + return 0 + } + + for _, kv := range kvs { + err := t.Put(kv.Key, kv.Value, stateVersion) + if err != nil { + logger.Errorf("failed putting key 0x%x and value 0x%x into trie: %s", + kv.Key, kv.Value, err) + return 0 + } + } + + // allocate memory for value and copy value to memory + ptr, err := rtCtx.Allocator.Allocate(32) + if err != nil { + logger.Errorf("failed allocating: %s", err) + return 0 + } + + hash, err := t.Hash() + if err != nil { + logger.Errorf("failed computing trie Merkle root hash: %s", err) + return 0 + } + + logger.Debugf("root hash is %s", hash) + m.Memory().Write(ptr, hash[:]) + return ptr +} + func ext_trie_blake2_256_ordered_root_version_1(ctx context.Context, m api.Module, dataSpan uint64) uint32 { rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) if rtCtx == nil { @@ -870,8 +926,62 @@ func ext_trie_blake2_256_ordered_root_version_1(ctx context.Context, m api.Modul func ext_trie_blake2_256_ordered_root_version_2( ctx context.Context, m api.Module, dataSpan uint64, version uint32) uint32 { - // TODO: update to use state trie version 1 (#2418) - return ext_trie_blake2_256_ordered_root_version_1(ctx, m, dataSpan) + rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) + if rtCtx == nil { + panic("nil runtime context") + } + + data := read(m, dataSpan) + + stateVersionBytes, _ := m.Memory().Read(version, 4) + stateVersion, err := trie.ParseVersion(binary.LittleEndian.Uint32(stateVersionBytes)) + if err != nil { + logger.Errorf("failed parsing state version: %s", err) + return 0 + } + + t := trie.NewEmptyTrie() + var values [][]byte + err = scale.Unmarshal(data, &values) + if err != nil { + logger.Errorf("failed scale decoding data: %s", err) + return 0 + } + + for i, value := range values { + key, err := scale.Marshal(big.NewInt(int64(i))) + if err != nil { + logger.Errorf("failed scale encoding value index %d: %s", i, err) + return 0 + } + logger.Tracef( + "put key=0x%x and value=0x%x", + key, value) + + err = t.Put(key, value, stateVersion) + if err != nil { + logger.Errorf("failed putting key 0x%x and value 0x%x into trie: %s", + key, value, err) + return 0 + } + } + + // allocate memory for value and copy value to memory + ptr, err := rtCtx.Allocator.Allocate(32) + if err != nil { + logger.Errorf("failed allocating: %s", err) + return 0 + } + + hash, err := t.Hash() + if err != nil { + logger.Errorf("failed computing trie Merkle root hash: %s", err) + return 0 + } + + logger.Debugf("root hash is %s", hash) + m.Memory().Write(ptr, hash[:]) + return ptr } func ext_trie_blake2_256_verify_proof_version_1( @@ -886,7 +996,46 @@ func ext_trie_blake2_256_verify_proof_version_1( err := scale.Unmarshal(toDecProofs, &encodedProofNodes) if err != nil { logger.Errorf("failed scale decoding proof data: %s", err) - return uint32(0) + return 0 + } + + key := read(m, keySpan) + value := read(m, valueSpan) + + trieRoot, ok := m.Memory().Read(rootSpan, 32) + if !ok { + panic("read overflow") + } + + err = proof.Verify(encodedProofNodes, trieRoot, key, value) + if err != nil { + logger.Errorf("failed proof verification: %s", err) + return 0 + } + + return 1 +} + +func ext_trie_blake2_256_verify_proof_version_2( + ctx context.Context, m api.Module, rootSpan uint32, proofSpan, keySpan, valueSpan uint64, version uint32) uint32 { + rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) + if rtCtx == nil { + panic("nil runtime context") + } + + stateVersionBytes, _ := m.Memory().Read(version, 4) + _, err := trie.ParseVersion(binary.LittleEndian.Uint32(stateVersionBytes)) + if err != nil { + logger.Errorf("failed parsing state version: %s", err) + return 0 + } + + toDecProofs := read(m, proofSpan) + var encodedProofNodes [][]byte + err = scale.Unmarshal(toDecProofs, &encodedProofNodes) + if err != nil { + logger.Errorf("failed scale decoding proof data: %s", err) + return 0 } key := read(m, keySpan) @@ -1214,9 +1363,30 @@ func ext_default_child_storage_root_version_1( //export ext_default_child_storage_root_version_2 func ext_default_child_storage_root_version_2(ctx context.Context, m api.Module, childStorageKey uint64, - stateVersion uint32) (ptrSize uint64) { - // TODO: Implement this after we have storage trie version 1 implemented #2418 - return ext_default_child_storage_root_version_1(ctx, m, childStorageKey) + stateVersion uint32) (ptrSize uint64) { //skipcq: RVV-B0012 + rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) + if rtCtx == nil { + panic("nil runtime context") + } + storage := rtCtx.Storage + child, err := storage.GetChild(read(m, childStorageKey)) + if err != nil { + logger.Errorf("failed to retrieve child: %s", err) + return mustWrite(m, rtCtx.Allocator, emptyByteVectorEncoded) + } + + childRoot, err := child.Hash() + if err != nil { + logger.Errorf("failed to encode child root: %s", err) + return mustWrite(m, rtCtx.Allocator, emptyByteVectorEncoded) + } + childRootSlice := childRoot[:] + + ret, err := write(m, rtCtx.Allocator, scale.MustMarshal(&childRootSlice)) + if err != nil { + panic(err) + } + return ret } func ext_default_child_storage_storage_kill_version_1(ctx context.Context, m api.Module, childStorageKeySpan uint64) { @@ -2188,8 +2358,33 @@ func ext_storage_root_version_1(ctx context.Context, m api.Module) uint64 { } func ext_storage_root_version_2(ctx context.Context, m api.Module, version uint32) uint64 { //skipcq: RVV-B0012 - // TODO: update to use state trie version 1 (#2418) - return ext_storage_root_version_1(ctx, m) + rtCtx := ctx.Value(runtimeContextKey).(*runtime.Context) + if rtCtx == nil { + panic("nil runtime context") + } + storage := rtCtx.Storage + + stateVersionBytes, _ := m.Memory().Read(version, 4) + _, err := trie.ParseVersion(binary.LittleEndian.Uint32(stateVersionBytes)) + if err != nil { + logger.Errorf("failed parsing state version: %s", err) + return mustWrite(m, rtCtx.Allocator, emptyByteVectorEncoded) + } + + root, err := storage.Root() + if err != nil { + logger.Errorf("failed to get storage root: %s", err) + panic(err) + } + + logger.Debugf("root hash is: %s", root) + + rootSpan, err := write(m, rtCtx.Allocator, root[:]) + if err != nil { + logger.Errorf("failed to allocate: %s", err) + panic(err) + } + return rootSpan } func ext_storage_set_version_1(ctx context.Context, m api.Module, keySpan, valueSpan uint64) { diff --git a/lib/runtime/wazero/imports_test.go b/lib/runtime/wazero/imports_test.go index 19afd980d3..e86ae08994 100644 --- a/lib/runtime/wazero/imports_test.go +++ b/lib/runtime/wazero/imports_test.go @@ -554,7 +554,9 @@ func Test_ext_trie_blake2_256_root_version_1(t *testing.T) { require.NoError(t, err) encInput[0] = encInput[0] >> 1 - res, err := inst.Exec("rtm_ext_trie_blake2_256_root_version_1", encInput) + data := encInput + + res, err := inst.Exec("rtm_ext_trie_blake2_256_root_version_1", data) require.NoError(t, err) var hash []byte @@ -569,6 +571,35 @@ func Test_ext_trie_blake2_256_root_version_1(t *testing.T) { require.Equal(t, expected[:], hash) } +func Test_ext_trie_blake2_256_root_version_2(t *testing.T) { + inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) + + testinput := []string{"dimartiro", "was", "here", "??"} + encInput, err := scale.Marshal(testinput) + require.NoError(t, err) + encInput[0] = encInput[0] >> 1 + + stateVersion := uint32(trie.V1) + stateVersionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(stateVersionBytes, stateVersion) + + data := append(encInput, stateVersionBytes...) + + res, err := inst.Exec("rtm_ext_trie_blake2_256_root_version_2", data) + require.NoError(t, err) + + var hash []byte + err = scale.Unmarshal(res, &hash) + require.NoError(t, err) + + tt := trie.NewEmptyTrie() + tt.Put([]byte("dimartiro"), []byte("was"), trie.V1) + tt.Put([]byte("here"), []byte("??"), trie.V1) + + expected := tt.MustHash() + require.Equal(t, expected[:], hash) +} + func Test_ext_trie_blake2_256_ordered_root_version_1(t *testing.T) { inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) @@ -587,6 +618,30 @@ func Test_ext_trie_blake2_256_ordered_root_version_1(t *testing.T) { require.Equal(t, expected[:], hash) } +func Test_ext_trie_blake2_256_ordered_root_version_2(t *testing.T) { + inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) + + testvalues := []string{"static", "even-keeled", "Future-proofed"} + encValues, err := scale.Marshal(testvalues) + require.NoError(t, err) + + stateVersion := uint32(trie.V1) + stateVersionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(stateVersionBytes, stateVersion) + + data := append(encValues, stateVersionBytes...) + + res, err := inst.Exec("rtm_ext_trie_blake2_256_ordered_root_version_2", data) + require.NoError(t, err) + + var hash []byte + err = scale.Unmarshal(res, &hash) + require.NoError(t, err) + + expected := common.MustHexToHash("0xd847b86d0219a384d11458e829e9f4f4cce7e3cc2e6dcd0e8a6ad6f12c64a737") + require.Equal(t, expected[:], hash) +} + func Test_ext_trie_blake2_256_verify_proof_version_1(t *testing.T) { tmp := t.TempDir() memdb, err := database.NewPebble(tmp, true) @@ -679,6 +734,104 @@ func Test_ext_trie_blake2_256_verify_proof_version_1(t *testing.T) { } } +func Test_ext_trie_blake2_256_verify_proof_version_2(t *testing.T) { + tmp := t.TempDir() + memdb, err := database.NewPebble(tmp, true) + require.NoError(t, err) + + otherTrie := trie.NewEmptyTrie() + otherTrie.Put([]byte("simple"), []byte("cat"), trie.V1) + + otherHash, err := otherTrie.Hash() + require.NoError(t, err) + + tr := trie.NewEmptyTrie() + tr.Put([]byte("do"), []byte("verb"), trie.V1) + tr.Put([]byte("domain"), []byte("website"), trie.V1) + tr.Put([]byte("other"), []byte("random"), trie.V1) + tr.Put([]byte("otherwise"), []byte("randomstuff"), trie.V1) + tr.Put([]byte("cat"), []byte("another animal"), trie.V1) + + err = tr.WriteDirty(memdb) + require.NoError(t, err) + + hash, err := tr.Hash() + require.NoError(t, err) + + keys := [][]byte{ + []byte("do"), + []byte("domain"), + []byte("other"), + []byte("otherwise"), + []byte("cat"), + } + + root := hash.ToBytes() + otherRoot := otherHash.ToBytes() + + allProofs, err := proof.Generate(root, keys, memdb) + require.NoError(t, err) + + testcases := map[string]struct { + root, key, value []byte + proof [][]byte + expect bool + }{ + "Proof_should_be_true": { + root: root, key: []byte("do"), proof: allProofs, value: []byte("verb"), expect: true}, + "Root_empty,_proof_should_be_false": { + root: []byte{}, key: []byte("do"), proof: allProofs, value: []byte("verb"), expect: false}, + "Other_root,_proof_should_be_false": { + root: otherRoot, key: []byte("do"), proof: allProofs, value: []byte("verb"), expect: false}, + "Value_empty,_proof_should_be_true": { + root: root, key: []byte("do"), proof: allProofs, value: nil, expect: true}, + "Unknow_key,_proof_should_be_false": { + root: root, key: []byte("unknow"), proof: allProofs, value: nil, expect: false}, + "Key_and_value_unknow,_proof_should_be_false": { + root: root, key: []byte("unknow"), proof: allProofs, value: []byte("unknow"), expect: false}, + "Empty_proof,_should_be_false": { + root: root, key: []byte("do"), proof: [][]byte{}, value: nil, expect: false}, + } + + inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) + + for name, testcase := range testcases { + testcase := testcase + t.Run(name, func(t *testing.T) { + hashEnc, err := scale.Marshal(testcase.root) + require.NoError(t, err) + + args := hashEnc + + encProof, err := scale.Marshal(testcase.proof) + require.NoError(t, err) + args = append(args, encProof...) + + keyEnc, err := scale.Marshal(testcase.key) + require.NoError(t, err) + args = append(args, keyEnc...) + + valueEnc, err := scale.Marshal(testcase.value) + require.NoError(t, err) + args = append(args, valueEnc...) + + stateVersion := uint32(trie.V1) + stateVersionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(stateVersionBytes, stateVersion) + + args = append(args, stateVersionBytes...) + + res, err := inst.Exec("rtm_ext_trie_blake2_256_verify_proof_version_2", args) + require.NoError(t, err) + + var got bool + err = scale.Unmarshal(res, &got) + require.NoError(t, err) + require.Equal(t, testcase.expect, got) + }) + } +} + func Test_ext_misc_runtime_version_version_1(t *testing.T) { inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) @@ -1149,6 +1302,45 @@ func Test_ext_default_child_storage_root_version_1(t *testing.T) { require.Equal(t, rootHash, actualValue) } +func Test_ext_default_child_storage_root_version_2(t *testing.T) { + inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) + + err := inst.Context.Storage.SetChild(testChildKey, trie.NewEmptyTrie(), trie.V1) + require.NoError(t, err) + + err = inst.Context.Storage.SetChildStorage(testChildKey, testKey, testValue, trie.V1) + require.NoError(t, err) + + child, err := inst.Context.Storage.GetChild(testChildKey) + require.NoError(t, err) + + rootHash, err := child.Hash() + require.NoError(t, err) + + encChildKey, err := scale.Marshal(testChildKey) + require.NoError(t, err) + encKey, err := scale.Marshal(testKey) + require.NoError(t, err) + + stateVersion := uint32(trie.V1) + encVersion, err := scale.Marshal(&stateVersion) + require.NoError(t, err) + + data := append(encChildKey, encKey...) + data = append(data, encVersion...) + + ret, err := inst.Exec("rtm_ext_default_child_storage_root_version_2", data) + require.NoError(t, err) + + var hash []byte + err = scale.Unmarshal(ret, &hash) + require.NoError(t, err) + + // Convert decoded interface to common Hash + actualValue := common.BytesToHash(hash) + require.Equal(t, rootHash, actualValue) +} + func Test_ext_default_child_storage_storage_kill_version_1(t *testing.T) { inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) @@ -1990,6 +2182,24 @@ func Test_ext_storage_root_version_1(t *testing.T) { require.Equal(t, expected[:], hash) } +func Test_ext_storage_root_version_2(t *testing.T) { + inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) + + stateVersion := uint32(trie.V1) + stateVersionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(stateVersionBytes, stateVersion) + + ret, err := inst.Exec("rtm_ext_storage_root_version_2", stateVersionBytes) + require.NoError(t, err) + + var hash []byte + err = scale.Unmarshal(ret, &hash) + require.NoError(t, err) + + expected := trie.EmptyHash + require.Equal(t, expected[:], hash) +} + func Test_ext_storage_set_version_1(t *testing.T) { inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME) diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index d9144facf3..de0ba4bf41 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -207,9 +207,7 @@ func NewInstance(code []byte, cfg Config) (instance *Instance, err error) { WithFunc(ext_trie_blake2_256_root_version_1). Export("ext_trie_blake2_256_root_version_1"). NewFunctionBuilder(). - WithFunc(func(a int64, v int32) int32 { - panic("ext_trie_blake2_256_root_version_2 unimplemented") - }). + WithFunc(ext_trie_blake2_256_root_version_2). Export("ext_trie_blake2_256_root_version_2"). NewFunctionBuilder(). WithFunc(ext_trie_blake2_256_ordered_root_version_1). @@ -221,9 +219,7 @@ func NewInstance(code []byte, cfg Config) (instance *Instance, err error) { WithFunc(ext_trie_blake2_256_verify_proof_version_1). Export("ext_trie_blake2_256_verify_proof_version_1"). NewFunctionBuilder(). - WithFunc(func(a int32, b int64, c int64, d int64, v int32) int32 { - panic("ext_trie_blake2_256_verify_proof_version_2 unimplemented") - }). + WithFunc(ext_trie_blake2_256_verify_proof_version_2). Export("ext_trie_blake2_256_verify_proof_version_2"). NewFunctionBuilder(). WithFunc(ext_misc_print_hex_version_1). diff --git a/lib/trie/database_test.go b/lib/trie/database_test.go index b05310729e..d512eb75ab 100644 --- a/lib/trie/database_test.go +++ b/lib/trie/database_test.go @@ -36,6 +36,15 @@ func Test_Trie_Store_Load(t *testing.T) { assert.Equal(t, trie.String(), trieFromDB.String()) } +func Test_Trie_Load_EmptyHash(t *testing.T) { + t.Parallel() + + db := newTestDB(t) + trieFromDB := NewEmptyTrie() + err := trieFromDB.Load(db, EmptyHash) + require.NoError(t, err) +} + func Test_Trie_WriteDirty_Put(t *testing.T) { t.Parallel() @@ -278,6 +287,17 @@ func Test_GetFromDB(t *testing.T) { } } +func Test_GetFromDB_EmptyHash(t *testing.T) { + t.Parallel() + + db := newTestDB(t) + + value, err := GetFromDB(db, EmptyHash, []byte("test")) + assert.NoError(t, err) + assert.Nil(t, value) + +} + func Test_Trie_PutChild_Store_Load(t *testing.T) { t.Parallel() diff --git a/lib/trie/version.go b/lib/trie/version.go index 7ccd6d1989..38366fbb09 100644 --- a/lib/trie/version.go +++ b/lib/trie/version.go @@ -53,7 +53,15 @@ func (v Version) ShouldHashValue(value []byte) bool { var ErrParseVersion = errors.New("parsing version failed") // ParseVersion parses a state trie version string. -func ParseVersion(s string) (version Version, err error) { +func ParseVersion[T string | uint32](v T) (version Version, err error) { + var s string + switch value := any(v).(type) { + case string: + s = value + case uint32: + s = fmt.Sprintf("V%d", value) + } + switch { case strings.EqualFold(s, V0.String()): return V0, nil diff --git a/lib/trie/version_test.go b/lib/trie/version_test.go index 93fe755b76..19892bdb1d 100644 --- a/lib/trie/version_test.go +++ b/lib/trie/version_test.go @@ -49,32 +49,45 @@ func Test_ParseVersion(t *testing.T) { t.Parallel() testCases := map[string]struct { - s string + v any version Version errWrapped error errMessage string }{ "v0": { - s: "v0", + v: "v0", version: V0, }, "V0": { - s: "V0", + v: "V0", + version: V0, + }, + "0": { + v: uint32(0), version: V0, }, "v1": { - s: "v1", + v: "v1", version: V1, }, "V1": { - s: "V1", + v: "V1", + version: V1, + }, + "1": { + v: uint32(1), version: V1, }, "invalid": { - s: "xyz", + v: "xyz", errWrapped: ErrParseVersion, errMessage: "parsing version failed: \"xyz\" must be one of [v0, v1]", }, + "invalid_uint32": { + v: uint32(999), + errWrapped: ErrParseVersion, + errMessage: "parsing version failed: \"V999\" must be one of [v0, v1]", + }, } for name, testCase := range testCases { @@ -82,7 +95,17 @@ func Test_ParseVersion(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - version, err := ParseVersion(testCase.s) + var version Version + + var err error + switch typed := testCase.v.(type) { + case string: + version, err = ParseVersion(typed) + case uint32: + version, err = ParseVersion(typed) + default: + t.Fail() + } assert.Equal(t, testCase.version, version) assert.ErrorIs(t, err, testCase.errWrapped)