From 24783b1284ef84714e78507a0cee216459d61827 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 09:01:54 +0800 Subject: [PATCH 01/34] systemcontracts for erigon --- systemcontracts/GenericStorage.sol | 230 ++++++++ systemcontracts/NamespaceStorage.sol | 386 +++++++++++++ systemcontracts/generic_storage.go | 439 +++++++++++++++ systemcontracts/generic_storage_abi.go | 301 ++++++++++ systemcontracts/namespace_storage.go | 680 +++++++++++++++++++++++ systemcontracts/namespace_storage_abi.go | 506 +++++++++++++++++ systemcontracts/systemcontracts.go | 141 +++++ 7 files changed, 2683 insertions(+) create mode 100644 systemcontracts/GenericStorage.sol create mode 100644 systemcontracts/NamespaceStorage.sol create mode 100644 systemcontracts/generic_storage.go create mode 100644 systemcontracts/generic_storage_abi.go create mode 100644 systemcontracts/namespace_storage.go create mode 100644 systemcontracts/namespace_storage_abi.go create mode 100644 systemcontracts/systemcontracts.go diff --git a/systemcontracts/GenericStorage.sol b/systemcontracts/GenericStorage.sol new file mode 100644 index 0000000000..33e586c3e2 --- /dev/null +++ b/systemcontracts/GenericStorage.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +/** + * @title GenericStorage + * @dev Generic storage contract for key-value data with flexible value structure + * This contract provides a universal storage solution with support for: + * - Basic CRUD operations (put, get, delete) + * - Batch operations (batchGet) + * - Listing all stored data + * - Flexible value structure with immutable and mutable fields + */ +contract GenericStorage { + + /** + * @dev Generic value structure with multiple fields for flexibility + * @param primaryData Primary data field for main content + * @param secondaryData Secondary data field for additional content + * @param auxiliaryData Additional data field for extended use cases + */ + struct GenericValue { + bytes primaryData; // Primary data field + bytes secondaryData; // Secondary data field + bytes auxiliaryData; // Additional data field for flexibility + } + + // Main storage mapping + mapping(bytes => GenericValue) private storage_; + + // Array to keep track of all keys for listing functionality + bytes[] private keys_; + + // Mapping to check if a key exists (for efficient existence checks) + mapping(bytes => bool) private keyExists_; + + // Events + event DataStored(bytes indexed key); + event DataDeleted(bytes indexed key); + event BatchDataRetrieved(uint256 keyCount); + event StorageCleared(); + + /** + * @dev Store data with a given key + * @param key The storage key + * @param value The GenericValue struct to store + */ + function put( + bytes memory key, + GenericValue memory value + ) external { + require(key.length > 0, "Key cannot be empty"); + + // If key doesn't exist, add it to keys array + if (!keyExists_[key]) { + keys_.push(key); + keyExists_[key] = true; + } + + // Store the value + storage_[key] = value; + + emit DataStored(key); + } + + /** + * @dev Delete data by key + * @param key The storage key to delete + */ + function remove(bytes memory key) external { + require(keyExists_[key], "Key does not exist"); + + // Remove from storage + delete storage_[key]; + keyExists_[key] = false; + + // Remove from keys array + for (uint256 i = 0; i < keys_.length; i++) { + if (keccak256(keys_[i]) == keccak256(key)) { + // Move last element to current position and pop + keys_[i] = keys_[keys_.length - 1]; + keys_.pop(); + break; + } + } + + emit DataDeleted(key); + } + + /** + * @dev Get data by key + * @param key The storage key + * @return value The stored GenericValue struct + * @return keyExists Whether the key exists + */ + function get(bytes memory key) external view returns (GenericValue memory value, bool keyExists) { + keyExists = keyExists_[key]; + if (keyExists) { + value = storage_[key]; + } + return (value, keyExists); + } + + /** + * @dev Batch get data by multiple keys + * @param keyList Array of keys to retrieve + * @return values Array of GenericValue structs + * @return existsFlags Array indicating which keys exist + */ + function batchGet(bytes[] memory keyList) external view returns ( + GenericValue[] memory values, + bool[] memory existsFlags + ) { + values = new GenericValue[](keyList.length); + existsFlags = new bool[](keyList.length); + + for (uint256 i = 0; i < keyList.length; i++) { + existsFlags[i] = keyExists_[keyList[i]]; + if (existsFlags[i]) { + values[i] = storage_[keyList[i]]; + } + } + + return (values, existsFlags); + } + + /** + * @dev List all stored data with pagination + * @param offset Starting index for pagination + * @param limit Maximum number of items to return + * @return keyList Array of keys + * @return values Array of corresponding GenericValue structs + * @return total Total number of stored items + */ + function list(uint256 offset, uint256 limit) external view returns ( + bytes[] memory keyList, + GenericValue[] memory values, + uint256 total + ) { + total = keys_.length; + + // Handle edge cases for pagination + if (offset >= total) { + keyList = new bytes[](0); + values = new GenericValue[](0); + return (keyList, values, total); + } + + // Calculate actual limit + uint256 remainingItems = total - offset; + uint256 actualLimit = limit > remainingItems ? remainingItems : limit; + + // Create result arrays + keyList = new bytes[](actualLimit); + values = new GenericValue[](actualLimit); + + // Fill result arrays + for (uint256 i = 0; i < actualLimit; i++) { + bytes memory key = keys_[offset + i]; + keyList[i] = key; + values[i] = storage_[key]; + } + + return (keyList, values, total); + } + + /** + * @dev List all keys only (lightweight version) + * @param offset Starting index for pagination + * @param limit Maximum number of keys to return + * @return keyList Array of keys + * @return total Total number of stored items + */ + function listKeys(uint256 offset, uint256 limit) external view returns ( + bytes[] memory keyList, + uint256 total + ) { + total = keys_.length; + + if (offset >= total) { + keyList = new bytes[](0); + return (keyList, total); + } + + uint256 remainingItems = total - offset; + uint256 actualLimit = limit > remainingItems ? remainingItems : limit; + + keyList = new bytes[](actualLimit); + + for (uint256 i = 0; i < actualLimit; i++) { + keyList[i] = keys_[offset + i]; + } + + return (keyList, total); + } + + /** + * @dev Check if a key exists + * @param key The storage key to check + * @return keyExists Whether the key exists + */ + function exists(bytes memory key) external view returns (bool keyExists) { + return keyExists_[key]; + } + + /** + * @dev Get total number of stored items + * @return totalCount Total number of items + */ + function count() external view returns (uint256 totalCount) { + return keys_.length; + } + + /** + * @dev Clear all stored data (emergency function) + * Note: This function should be carefully protected in production + */ + function clear() external { + // Clear all mappings and arrays + for (uint256 i = 0; i < keys_.length; i++) { + bytes memory key = keys_[i]; + delete storage_[key]; + keyExists_[key] = false; + } + + // Clear keys array + delete keys_; + + emit StorageCleared(); + } +} \ No newline at end of file diff --git a/systemcontracts/NamespaceStorage.sol b/systemcontracts/NamespaceStorage.sol new file mode 100644 index 0000000000..1ae024cf80 --- /dev/null +++ b/systemcontracts/NamespaceStorage.sol @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +/** + * @title NamespaceStorage + * @dev Namespace-aware storage contract for key-value data with flexible value structure + * This contract extends the GenericStorage concept with namespace support for data isolation + */ +contract NamespaceStorage { + + /** + * @dev Generic value structure with multiple fields for flexibility + * @param primaryData Primary data field for main content + * @param secondaryData Secondary data field for additional content + * @param auxiliaryData Additional data field for extended use cases + */ + struct GenericValue { + bytes primaryData; // Primary data field + bytes secondaryData; // Secondary data field + bytes auxiliaryData; // Additional data field for flexibility + } + + // Nested mapping: namespace => key => value + mapping(string => mapping(bytes => GenericValue)) private namespaceStorage_; + + // Track keys for each namespace + mapping(string => bytes[]) private namespaceKeys_; + + // Track if a key exists in a namespace + mapping(string => mapping(bytes => bool)) private keyExists_; + + // Track all namespaces + string[] private namespaces_; + mapping(string => bool) private namespaceExists_; + + // Events for tracking operations + event DataStored(string indexed namespace, bytes indexed key); + event DataDeleted(string indexed namespace, bytes indexed key); + event BatchDataRetrieved(string indexed namespace, uint256 keyCount); + event NamespaceCleared(string indexed namespace); + event AllDataCleared(); + + /** + * @dev Store data with a given namespace and key + * @param namespace The namespace for data isolation + * @param key The storage key within the namespace + * @param value The GenericValue struct to store + */ + function put( + string memory namespace, + bytes memory key, + GenericValue memory value + ) external { + require(bytes(namespace).length > 0, "Namespace cannot be empty"); + require(key.length > 0, "Key cannot be empty"); + + // Add namespace if it doesn't exist + if (!namespaceExists_[namespace]) { + namespaces_.push(namespace); + namespaceExists_[namespace] = true; + } + + // If key doesn't exist in this namespace, add it to keys array + if (!keyExists_[namespace][key]) { + namespaceKeys_[namespace].push(key); + keyExists_[namespace][key] = true; + } + + // Store the value + namespaceStorage_[namespace][key] = value; + + emit DataStored(namespace, key); + } + + /** + * @dev Get data by namespace and key + * @param namespace The namespace containing the key + * @param key The storage key + * @return value The stored GenericValue struct + * @return keyExists Whether the key exists in the namespace + */ + function get(string memory namespace, bytes memory key) external view returns ( + GenericValue memory value, + bool keyExists + ) { + keyExists = keyExists_[namespace][key]; + if (keyExists) { + value = namespaceStorage_[namespace][key]; + } + return (value, keyExists); + } + + /** + * @dev Delete data by namespace and key + * @param namespace The namespace containing the key + * @param key The storage key to delete + */ + function remove(string memory namespace, bytes memory key) external { + require(namespaceExists_[namespace], "Namespace does not exist"); + require(keyExists_[namespace][key], "Key does not exist in namespace"); + + // Remove from storage + delete namespaceStorage_[namespace][key]; + keyExists_[namespace][key] = false; + + // Remove from keys array + bytes[] storage keys = namespaceKeys_[namespace]; + for (uint256 i = 0; i < keys.length; i++) { + if (keccak256(keys[i]) == keccak256(key)) { + // Move last element to current position and pop + keys[i] = keys[keys.length - 1]; + keys.pop(); + break; + } + } + + emit DataDeleted(namespace, key); + } + + /** + * @dev Batch get data by multiple keys within a namespace + * @param namespace The namespace to query + * @param keyList Array of keys to retrieve + * @return values Array of GenericValue structs + * @return existsFlags Array indicating which keys exist + */ + function batchGet(string memory namespace, bytes[] memory keyList) external view returns ( + GenericValue[] memory values, + bool[] memory existsFlags + ) { + values = new GenericValue[](keyList.length); + existsFlags = new bool[](keyList.length); + + for (uint256 i = 0; i < keyList.length; i++) { + existsFlags[i] = keyExists_[namespace][keyList[i]]; + if (existsFlags[i]) { + values[i] = namespaceStorage_[namespace][keyList[i]]; + } + } + + return (values, existsFlags); + } + + /** + * @dev Batch put multiple key-value pairs in the same namespace + * @param namespace The namespace for all data + * @param keys Array of keys to store + * @param values Array of values to store + */ + function batchPut( + string memory namespace, + bytes[] memory keys, + GenericValue[] memory values + ) external { + require(keys.length == values.length, "Keys and values arrays must have same length"); + require(bytes(namespace).length > 0, "Namespace cannot be empty"); + + // Add namespace if it doesn't exist + if (!namespaceExists_[namespace]) { + namespaces_.push(namespace); + namespaceExists_[namespace] = true; + } + + for (uint256 i = 0; i < keys.length; i++) { + require(keys[i].length > 0, "Key cannot be empty"); + + // If key doesn't exist in this namespace, add it to keys array + if (!keyExists_[namespace][keys[i]]) { + namespaceKeys_[namespace].push(keys[i]); + keyExists_[namespace][keys[i]] = true; + } + + // Store the value + namespaceStorage_[namespace][keys[i]] = values[i]; + + emit DataStored(namespace, keys[i]); + } + } + + /** + * @dev List all stored data in a namespace with pagination + * @param namespace The namespace to list + * @param offset Starting index for pagination + * @param limit Maximum number of items to return + * @return keyList Array of keys + * @return values Array of corresponding GenericValue structs + * @return total Total number of stored items in the namespace + */ + function list(string memory namespace, uint256 offset, uint256 limit) external view returns ( + bytes[] memory keyList, + GenericValue[] memory values, + uint256 total + ) { + bytes[] storage keys = namespaceKeys_[namespace]; + total = keys.length; + + // Handle edge cases for pagination + if (offset >= total) { + keyList = new bytes[](0); + values = new GenericValue[](0); + return (keyList, values, total); + } + + // Calculate actual limit + uint256 remainingItems = total - offset; + uint256 actualLimit = limit > remainingItems ? remainingItems : limit; + + // Create result arrays + keyList = new bytes[](actualLimit); + values = new GenericValue[](actualLimit); + + // Fill result arrays + for (uint256 i = 0; i < actualLimit; i++) { + bytes memory key = keys[offset + i]; + keyList[i] = key; + values[i] = namespaceStorage_[namespace][key]; + } + + return (keyList, values, total); + } + + /** + * @dev List all keys in a namespace (lightweight version) + * @param namespace The namespace to list + * @param offset Starting index for pagination + * @param limit Maximum number of keys to return + * @return keyList Array of keys + * @return total Total number of stored items in the namespace + */ + function listKeys(string memory namespace, uint256 offset, uint256 limit) external view returns ( + bytes[] memory keyList, + uint256 total + ) { + bytes[] storage keys = namespaceKeys_[namespace]; + total = keys.length; + + if (offset >= total) { + keyList = new bytes[](0); + return (keyList, total); + } + + uint256 remainingItems = total - offset; + uint256 actualLimit = limit > remainingItems ? remainingItems : limit; + + keyList = new bytes[](actualLimit); + + for (uint256 i = 0; i < actualLimit; i++) { + keyList[i] = keys[offset + i]; + } + + return (keyList, total); + } + + /** + * @dev List all namespaces with pagination + * @param offset Starting index for pagination + * @param limit Maximum number of namespaces to return + * @return namespaceList Array of namespace names + * @return counts Array of item counts for each namespace + * @return total Total number of namespaces + */ + function listNamespaces(uint256 offset, uint256 limit) external view returns ( + string[] memory namespaceList, + uint256[] memory counts, + uint256 total + ) { + total = namespaces_.length; + + if (offset >= total) { + namespaceList = new string[](0); + counts = new uint256[](0); + return (namespaceList, counts, total); + } + + uint256 remainingItems = total - offset; + uint256 actualLimit = limit > remainingItems ? remainingItems : limit; + + namespaceList = new string[](actualLimit); + counts = new uint256[](actualLimit); + + for (uint256 i = 0; i < actualLimit; i++) { + string memory ns = namespaces_[offset + i]; + namespaceList[i] = ns; + counts[i] = namespaceKeys_[ns].length; + } + + return (namespaceList, counts, total); + } + + /** + * @dev Check if a key exists in a namespace + * @param namespace The namespace to check + * @param key The storage key to check + * @return keyExists Whether the key exists in the namespace + */ + function exists(string memory namespace, bytes memory key) external view returns (bool keyExists) { + return keyExists_[namespace][key]; + } + + /** + * @dev Check if a namespace exists + * @param namespace The namespace to check + * @return nsExists Whether the namespace exists + */ + function hasNamespace(string memory namespace) external view returns (bool nsExists) { + return namespaceExists_[namespace]; + } + + /** + * @dev Get total number of stored items in a namespace + * @param namespace The namespace to count + * @return itemCount Total number of items in the namespace + */ + function countInNamespace(string memory namespace) external view returns (uint256 itemCount) { + return namespaceKeys_[namespace].length; + } + + /** + * @dev Get total number of namespaces + * @return totalNamespaces Total number of namespaces + */ + function namespaceCount() external view returns (uint256 totalNamespaces) { + return namespaces_.length; + } + + /** + * @dev Get total number of items across all namespaces + * @return totalItems Total number of items across all namespaces + */ + function totalCount() external view returns (uint256 totalItems) { + for (uint256 i = 0; i < namespaces_.length; i++) { + totalItems += namespaceKeys_[namespaces_[i]].length; + } + return totalItems; + } + + /** + * @dev Clear all data in a specific namespace + * @param namespace The namespace to clear + */ + function clearNamespace(string memory namespace) external { + require(namespaceExists_[namespace], "Namespace does not exist"); + + bytes[] storage keys = namespaceKeys_[namespace]; + + // Clear all data in the namespace + for (uint256 i = 0; i < keys.length; i++) { + bytes memory key = keys[i]; + delete namespaceStorage_[namespace][key]; + keyExists_[namespace][key] = false; + } + + // Clear keys array for the namespace + delete namespaceKeys_[namespace]; + + emit NamespaceCleared(namespace); + } + + /** + * @dev Clear all stored data across all namespaces (emergency function) + * Note: This function should be carefully protected in production + */ + function clearAll() external { + // Clear all namespaces + for (uint256 i = 0; i < namespaces_.length; i++) { + string memory namespace = namespaces_[i]; + bytes[] storage keys = namespaceKeys_[namespace]; + + // Clear all data in this namespace + for (uint256 j = 0; j < keys.length; j++) { + bytes memory key = keys[j]; + delete namespaceStorage_[namespace][key]; + keyExists_[namespace][key] = false; + } + + // Clear keys array for this namespace + delete namespaceKeys_[namespace]; + namespaceExists_[namespace] = false; + } + + // Clear namespaces array + delete namespaces_; + + emit AllDataCleared(); + } +} \ No newline at end of file diff --git a/systemcontracts/generic_storage.go b/systemcontracts/generic_storage.go new file mode 100644 index 0000000000..66163ed0c5 --- /dev/null +++ b/systemcontracts/generic_storage.go @@ -0,0 +1,439 @@ +package systemcontracts + +import ( + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +// GenericValue represents the value structure in the GenericStorage contract +type GenericValue struct { + PrimaryData []byte `json:"primaryData"` + SecondaryData []byte `json:"secondaryData"` + AuxiliaryData []byte `json:"auxiliaryData"` +} + +// BatchGetResult represents the result of a batch get operation +type BatchGetResult struct { + Values []GenericValue `json:"values"` + ExistsFlags []bool `json:"existsFlags"` +} + +// ListResult represents the result of a list operation +type ListResult struct { + KeyList [][]byte `json:"keyList"` + Values []GenericValue `json:"values"` + Total *big.Int `json:"total"` +} + +// ListKeysResult represents the result of a listKeys operation +type ListKeysResult struct { + KeyList [][]byte `json:"keyList"` + Total *big.Int `json:"total"` +} + +// GetResult represents the result of a get operation +type GetResult struct { + Value GenericValue `json:"value"` + KeyExists bool `json:"keyExists"` +} + +// GenericStorageContract provides an interface to interact with the GenericStorage smart contract +type GenericStorageContract struct { + contractAddress common.Address + backend ContractBackend + abi abi.ABI +} + +// NewGenericStorageContract creates a new GenericStorage contract instance +func NewGenericStorageContract(contractAddress common.Address, backend ContractBackend) (*GenericStorageContract, error) { + abi, err := abi.JSON(strings.NewReader(GenericStorageABI)) + if err != nil { + return nil, errors.Wrap(err, "failed to parse GenericStorage ABI") + } + + return &GenericStorageContract{ + contractAddress: contractAddress, + backend: backend, + abi: abi, + }, nil +} + +func (g *GenericStorageContract) Address() common.Address { + return g.contractAddress +} + +// Put stores data with the given key +func (g *GenericStorageContract) Put(key []byte, value GenericValue) error { + // Validate input + if len(key) == 0 { + return errors.New("key cannot be empty") + } + + // Pack the function call + data, err := g.abi.Pack("put", key, value) + if err != nil { + return errors.Wrap(err, "failed to pack put call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := g.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute put") + } + + log.L().Debug("Successfully stored data", + zap.String("key", string(key)), + zap.Int("primaryDataSize", len(value.PrimaryData)), + zap.Int("secondaryDataSize", len(value.SecondaryData)), + zap.Int("auxiliaryDataSize", len(value.AuxiliaryData))) + + return nil +} + +// PutSimple stores data with only primary data (convenience method) +func (g *GenericStorageContract) PutSimple(key []byte, data []byte) error { + value := GenericValue{ + PrimaryData: data, + SecondaryData: []byte{}, + AuxiliaryData: []byte{}, + } + return g.Put(key, value) +} + +// Get retrieves data by key +func (g *GenericStorageContract) Get(key []byte) (*GetResult, error) { + // Validate input + if len(key) == 0 { + return nil, errors.New("key cannot be empty") + } + + // Pack the function call + data, err := g.abi.Pack("get", key) + if err != nil { + return nil, errors.Wrap(err, "failed to pack get call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call get") + } + + // Unpack the result + var getResult struct { + Value GenericValue + KeyExists bool + } + + err = g.abi.UnpackIntoInterface(&getResult, "get", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack get result") + } + + log.L().Debug("Successfully retrieved data", + zap.String("key", string(key)), + zap.Bool("exists", getResult.KeyExists)) + + return &GetResult{ + Value: getResult.Value, + KeyExists: getResult.KeyExists, + }, nil +} + +// Remove deletes data by key +func (g *GenericStorageContract) Remove(key []byte) error { + // Validate input + if len(key) == 0 { + return errors.New("key cannot be empty") + } + + // Pack the function call + data, err := g.abi.Pack("remove", key) + if err != nil { + return errors.Wrap(err, "failed to pack remove call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := g.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute remove") + } + + log.L().Debug("Successfully removed data", + zap.String("key", string(key))) + + return nil +} + +// BatchGet retrieves multiple values by their keys +func (g *GenericStorageContract) BatchGet(keys [][]byte) (*BatchGetResult, error) { + // Validate input + if len(keys) == 0 { + return &BatchGetResult{ + Values: []GenericValue{}, + ExistsFlags: []bool{}, + }, nil + } + + // Pack the function call + data, err := g.abi.Pack("batchGet", keys) + if err != nil { + return nil, errors.Wrap(err, "failed to pack batchGet call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call batchGet") + } + + // Unpack the result + var batchGetResult struct { + Values []GenericValue + ExistsFlags []bool + } + + err = g.abi.UnpackIntoInterface(&batchGetResult, "batchGet", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack batchGet result") + } + + log.L().Debug("Successfully batch retrieved data", + zap.Int("requestedKeys", len(keys)), + zap.Int("returnedValues", len(batchGetResult.Values))) + + return &BatchGetResult{ + Values: batchGetResult.Values, + ExistsFlags: batchGetResult.ExistsFlags, + }, nil +} + +// List retrieves all stored data with pagination +func (g *GenericStorageContract) List(offset, limit uint64) (*ListResult, error) { + // Pack the function call + data, err := g.abi.Pack("list", new(big.Int).SetUint64(offset), new(big.Int).SetUint64(limit)) + if err != nil { + return nil, errors.Wrap(err, "failed to pack list call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call list") + } + + // Unpack the result + var listResult struct { + KeyList [][]byte + Values []GenericValue + Total *big.Int + } + + err = g.abi.UnpackIntoInterface(&listResult, "list", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack list result") + } + + log.L().Debug("Successfully listed data", + zap.Uint64("offset", offset), + zap.Uint64("limit", limit), + zap.Int("returned", len(listResult.Values)), + zap.String("total", listResult.Total.String())) + + return &ListResult{ + KeyList: listResult.KeyList, + Values: listResult.Values, + Total: listResult.Total, + }, nil +} + +// ListKeys retrieves all keys with pagination (lightweight version) +func (g *GenericStorageContract) ListKeys(offset, limit uint64) (*ListKeysResult, error) { + // Pack the function call + data, err := g.abi.Pack("listKeys", new(big.Int).SetUint64(offset), new(big.Int).SetUint64(limit)) + if err != nil { + return nil, errors.Wrap(err, "failed to pack listKeys call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call listKeys") + } + + // Unpack the result + var listKeysResult struct { + KeyList [][]byte + Total *big.Int + } + + err = g.abi.UnpackIntoInterface(&listKeysResult, "listKeys", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack listKeys result") + } + + log.L().Debug("Successfully listed keys", + zap.Uint64("offset", offset), + zap.Uint64("limit", limit), + zap.Int("returned", len(listKeysResult.KeyList)), + zap.String("total", listKeysResult.Total.String())) + + return &ListKeysResult{ + KeyList: listKeysResult.KeyList, + Total: listKeysResult.Total, + }, nil +} + +// Exists checks if a key exists in the storage +func (g *GenericStorageContract) Exists(key []byte) (bool, error) { + // Validate input + if len(key) == 0 { + return false, errors.New("key cannot be empty") + } + + // Pack the function call + data, err := g.abi.Pack("exists", key) + if err != nil { + return false, errors.Wrap(err, "failed to pack exists call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return false, errors.Wrap(err, "failed to call exists") + } + + // Unpack the result + var existsResult struct { + KeyExists bool + } + + err = g.abi.UnpackIntoInterface(&existsResult, "exists", result) + if err != nil { + return false, errors.Wrap(err, "failed to unpack exists result") + } + + log.L().Debug("Successfully checked key existence", + zap.String("key", string(key)), + zap.Bool("exists", existsResult.KeyExists)) + + return existsResult.KeyExists, nil +} + +// Count returns the total number of stored items +func (g *GenericStorageContract) Count() (*big.Int, error) { + // Pack the function call + data, err := g.abi.Pack("count") + if err != nil { + return nil, errors.Wrap(err, "failed to pack count call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := g.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call count") + } + + // Unpack the result + var countResult struct { + TotalCount *big.Int + } + + err = g.abi.UnpackIntoInterface(&countResult, "count", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack count result") + } + + log.L().Debug("Successfully retrieved count", + zap.String("count", countResult.TotalCount.String())) + + return countResult.TotalCount, nil +} + +// Clear removes all stored data (emergency function) +func (g *GenericStorageContract) Clear() error { + // Pack the function call + data, err := g.abi.Pack("clear") + if err != nil { + return errors.Wrap(err, "failed to pack clear call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &g.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := g.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute clear") + } + + log.L().Debug("Successfully cleared all data") + + return nil +} diff --git a/systemcontracts/generic_storage_abi.go b/systemcontracts/generic_storage_abi.go new file mode 100644 index 0000000000..62d8977d3e --- /dev/null +++ b/systemcontracts/generic_storage_abi.go @@ -0,0 +1,301 @@ +package systemcontracts + +// GenericStorageABI is the ABI definition for the GenericStorage contract +const GenericStorageABI = `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "DataDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "DataStored", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "keyCount", + "type": "uint256" + } + ], + "name": "BatchDataRetrieved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StorageCleared", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + } + ], + "name": "batchGet", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct GenericStorage.GenericValue[]", + "name": "values", + "type": "tuple[]" + }, + { + "internalType": "bool[]", + "name": "existsFlags", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "clear", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "count", + "outputs": [ + { + "internalType": "uint256", + "name": "totalCount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "exists", + "outputs": [ + { + "internalType": "bool", + "name": "keyExists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "get", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct GenericStorage.GenericValue", + "name": "value", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "keyExists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "list", + "outputs": [ + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct GenericStorage.GenericValue[]", + "name": "values", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "listKeys", + "outputs": [ + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct GenericStorage.GenericValue", + "name": "value", + "type": "tuple" + } + ], + "name": "put", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "remove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]` + +const GenericStorageByteCodeStr = "608060405234801561001057600080fd5b50612437806100206000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c806352efea6e1161006657806352efea6e1461013557806358edef4c1461013f57806379fc09a21461015b578063821bef2f1461018b578063d6d7d525146101bc57610093565b806306661abd14610098578063072957b0146100b657806342e1ae2e146100d257806350fd736714610103575b600080fd5b6100a06101ed565b6040516100ad9190611423565b60405180910390f35b6100d060048036038101906100cb919061165a565b6101fa565b005b6100ec60048036038101906100e791906117b8565b610385565b6040516100fa929190611a6a565b60405180910390f35b61011d60048036038101906101189190611acd565b6106ed565b60405161012c93929190611bcf565b60405180910390f35b61013d610b6d565b005b61015960048036038101906101549190611c14565b610cfb565b005b61017560048036038101906101709190611c14565b610f1e565b6040516101829190611c6c565b60405180910390f35b6101a560048036038101906101a09190611acd565b610f53565b6040516101b3929190611c87565b60405180910390f35b6101d660048036038101906101d19190611c14565b611122565b6040516101e4929190611d15565b60405180910390f35b6000600180549050905090565b600082511161023e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023590611da2565b60405180910390fd5b60028260405161024e9190611dfe565b908152602001604051809103902060009054906101000a900460ff166102db576001829080600181540180825580915050600190039060005260206000200160009091909190915090816102a29190612021565b5060016002836040516102b59190611dfe565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b806000836040516102ec9190611dfe565b9081526020016040518091039020600082015181600001908161030f9190612021565b5060208201518160010190816103259190612021565b50604082015181600201908161033b9190612021565b509050508160405161034d9190611dfe565b60405180910390207f281be1a469724e78f8c4152bcc9fb70a95646c3e1e2ade9293d8d497c976338a60405160405180910390a25050565b606080825167ffffffffffffffff8111156103a3576103a261146d565b5b6040519080825280602002602001820160405280156103dc57816020015b6103c9611347565b8152602001906001900390816103c15790505b509150825167ffffffffffffffff8111156103fa576103f961146d565b5b6040519080825280602002602001820160405280156104285781602001602082028036833780820191505090505b50905060005b83518110156106e757600284828151811061044c5761044b6120f3565b5b60200260200101516040516104619190611dfe565b908152602001604051809103902060009054906101000a900460ff168282815181106104905761048f6120f3565b5b6020026020010190151590811515815250508181815181106104b5576104b46120f3565b5b6020026020010151156106da5760008482815181106104d7576104d66120f3565b5b60200260200101516040516104ec9190611dfe565b908152602001604051809103902060405180606001604052908160008201805461051590611e44565b80601f016020809104026020016040519081016040528092919081815260200182805461054190611e44565b801561058e5780601f106105635761010080835404028352916020019161058e565b820191906000526020600020905b81548152906001019060200180831161057157829003601f168201915b505050505081526020016001820180546105a790611e44565b80601f01602080910402602001604051908101604052809291908181526020018280546105d390611e44565b80156106205780601f106105f557610100808354040283529160200191610620565b820191906000526020600020905b81548152906001019060200180831161060357829003601f168201915b5050505050815260200160028201805461063990611e44565b80601f016020809104026020016040519081016040528092919081815260200182805461066590611e44565b80156106b25780601f10610687576101008083540402835291602001916106b2565b820191906000526020600020905b81548152906001019060200180831161069557829003601f168201915b5050505050815250508382815181106106ce576106cd6120f3565b5b60200260200101819052505b808060010191505061042e565b50915091565b606080600060018054905090508085106107ae57600067ffffffffffffffff81111561071c5761071b61146d565b5b60405190808252806020026020018201604052801561074f57816020015b606081526020019060019003908161073a5790505b509250600067ffffffffffffffff81111561076d5761076c61146d565b5b6040519080825280602002602001820160405280156107a657816020015b610793611347565b81526020019060019003908161078b5790505b509150610b66565b600085826107bc9190612151565b905060008186116107cd57856107cf565b815b90508067ffffffffffffffff8111156107eb576107ea61146d565b5b60405190808252806020026020018201604052801561081e57816020015b60608152602001906001900390816108095790505b5094508067ffffffffffffffff81111561083b5761083a61146d565b5b60405190808252806020026020018201604052801561087457816020015b610861611347565b8152602001906001900390816108595790505b50935060005b81811015610b625760006001828a6108929190612185565b815481106108a3576108a26120f3565b5b9060005260206000200180546108b890611e44565b80601f01602080910402602001604051908101604052809291908181526020018280546108e490611e44565b80156109315780601f1061090657610100808354040283529160200191610931565b820191906000526020600020905b81548152906001019060200180831161091457829003601f168201915b505050505090508087838151811061094c5761094b6120f3565b5b60200260200101819052506000816040516109679190611dfe565b908152602001604051809103902060405180606001604052908160008201805461099090611e44565b80601f01602080910402602001604051908101604052809291908181526020018280546109bc90611e44565b8015610a095780601f106109de57610100808354040283529160200191610a09565b820191906000526020600020905b8154815290600101906020018083116109ec57829003601f168201915b50505050508152602001600182018054610a2290611e44565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4e90611e44565b8015610a9b5780601f10610a7057610100808354040283529160200191610a9b565b820191906000526020600020905b815481529060010190602001808311610a7e57829003601f168201915b50505050508152602001600282018054610ab490611e44565b80601f0160208091040260200160405190810160405280929190818152602001828054610ae090611e44565b8015610b2d5780601f10610b0257610100808354040283529160200191610b2d565b820191906000526020600020905b815481529060010190602001808311610b1057829003601f168201915b505050505081525050868381518110610b4957610b486120f3565b5b602002602001018190525050808060010191505061087a565b5050505b9250925092565b60005b600180549050811015610cbe57600060018281548110610b9357610b926120f3565b5b906000526020600020018054610ba890611e44565b80601f0160208091040260200160405190810160405280929190818152602001828054610bd490611e44565b8015610c215780601f10610bf657610100808354040283529160200191610c21565b820191906000526020600020905b815481529060010190602001808311610c0457829003601f168201915b50505050509050600081604051610c389190611dfe565b908152602001604051809103902060008082016000610c579190611368565b600182016000610c679190611368565b600282016000610c779190611368565b50506000600282604051610c8b9190611dfe565b908152602001604051809103902060006101000a81548160ff021916908315150217905550508080600101915050610b70565b5060016000610ccd91906113a8565b7f6c7e419df39ff46e811e7d979ebfd916b8d4960de06f479dc69cf8417cdb89e760405160405180910390a1565b600281604051610d0b9190611dfe565b908152602001604051809103902060009054906101000a900460ff16610d66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d5d90612205565b60405180910390fd5b600081604051610d769190611dfe565b908152602001604051809103902060008082016000610d959190611368565b600182016000610da59190611368565b600282016000610db59190611368565b50506000600282604051610dc99190611dfe565b908152602001604051809103902060006101000a81548160ff02191690831515021790555060005b600180549050811015610ed857818051906020012060018281548110610e1a57610e196120f3565b5b90600052602060002001604051610e3191906122a8565b604051809103902003610ecb576001808080549050610e509190612151565b81548110610e6157610e606120f3565b5b9060005260206000200160018281548110610e7f57610e7e6120f3565b5b906000526020600020019081610e9591906122ea565b506001805480610ea857610ea76123d2565b5b600190038181906000526020600020016000610ec49190611368565b9055610ed8565b8080600101915050610df1565b5080604051610ee79190611dfe565b60405180910390207f9248955bf73fd007be0a0d9dc13c36f9e61c3642fbac04d8ac592e0c21d0103760405160405180910390a250565b6000600282604051610f309190611dfe565b908152602001604051809103902060009054906101000a900460ff169050919050565b606060006001805490509050808410610fbc57600067ffffffffffffffff811115610f8157610f8061146d565b5b604051908082528060200260200182016040528015610fb457816020015b6060815260200190600190039081610f9f5790505b50915061111b565b60008482610fca9190612151565b90506000818511610fdb5784610fdd565b815b90508067ffffffffffffffff811115610ff957610ff861146d565b5b60405190808252806020026020018201604052801561102c57816020015b60608152602001906001900390816110175790505b50935060005b8181101561111757600181886110489190612185565b81548110611059576110586120f3565b5b90600052602060002001805461106e90611e44565b80601f016020809104026020016040519081016040528092919081815260200182805461109a90611e44565b80156110e75780601f106110bc576101008083540402835291602001916110e7565b820191906000526020600020905b8154815290600101906020018083116110ca57829003601f168201915b50505050508582815181106110ff576110fe6120f3565b5b60200260200101819052508080600101915050611032565b5050505b9250929050565b61112a611347565b600060028360405161113c9190611dfe565b908152602001604051809103902060009054906101000a900460ff1690508015611342576000836040516111709190611dfe565b908152602001604051809103902060405180606001604052908160008201805461119990611e44565b80601f01602080910402602001604051908101604052809291908181526020018280546111c590611e44565b80156112125780601f106111e757610100808354040283529160200191611212565b820191906000526020600020905b8154815290600101906020018083116111f557829003601f168201915b5050505050815260200160018201805461122b90611e44565b80601f016020809104026020016040519081016040528092919081815260200182805461125790611e44565b80156112a45780601f10611279576101008083540402835291602001916112a4565b820191906000526020600020905b81548152906001019060200180831161128757829003601f168201915b505050505081526020016002820180546112bd90611e44565b80601f01602080910402602001604051908101604052809291908181526020018280546112e990611e44565b80156113365780601f1061130b57610100808354040283529160200191611336565b820191906000526020600020905b81548152906001019060200180831161131957829003601f168201915b50505050508152505091505b915091565b60405180606001604052806060815260200160608152602001606081525090565b50805461137490611e44565b6000825580601f1061138657506113a5565b601f0160209004906000526020600020908101906113a491906113c9565b5b50565b50805460008255906000526020600020908101906113c691906113e6565b50565b5b808211156113e25760008160009055506001016113ca565b5090565b5b8082111561140657600081816113fd9190611368565b506001016113e7565b5090565b6000819050919050565b61141d8161140a565b82525050565b60006020820190506114386000830184611414565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6114a58261145c565b810181811067ffffffffffffffff821117156114c4576114c361146d565b5b80604052505050565b60006114d761143e565b90506114e3828261149c565b919050565b600067ffffffffffffffff8211156115035761150261146d565b5b61150c8261145c565b9050602081019050919050565b82818337600083830152505050565b600061153b611536846114e8565b6114cd565b90508281526020810184848401111561155757611556611457565b5b611562848285611519565b509392505050565b600082601f83011261157f5761157e611452565b5b813561158f848260208601611528565b91505092915050565b600080fd5b600080fd5b6000606082840312156115b8576115b7611598565b5b6115c260606114cd565b9050600082013567ffffffffffffffff8111156115e2576115e161159d565b5b6115ee8482850161156a565b600083015250602082013567ffffffffffffffff8111156116125761161161159d565b5b61161e8482850161156a565b602083015250604082013567ffffffffffffffff8111156116425761164161159d565b5b61164e8482850161156a565b60408301525092915050565b6000806040838503121561167157611670611448565b5b600083013567ffffffffffffffff81111561168f5761168e61144d565b5b61169b8582860161156a565b925050602083013567ffffffffffffffff8111156116bc576116bb61144d565b5b6116c8858286016115a2565b9150509250929050565b600067ffffffffffffffff8211156116ed576116ec61146d565b5b602082029050602081019050919050565b600080fd5b6000611716611711846116d2565b6114cd565b90508083825260208201905060208402830185811115611739576117386116fe565b5b835b8181101561178057803567ffffffffffffffff81111561175e5761175d611452565b5b80860161176b898261156a565b8552602085019450505060208101905061173b565b5050509392505050565b600082601f83011261179f5761179e611452565b5b81356117af848260208601611703565b91505092915050565b6000602082840312156117ce576117cd611448565b5b600082013567ffffffffffffffff8111156117ec576117eb61144d565b5b6117f88482850161178a565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561186757808201518184015260208101905061184c565b60008484015250505050565b600061187e8261182d565b6118888185611838565b9350611898818560208601611849565b6118a18161145c565b840191505092915050565b600060608301600083015184820360008601526118c98282611873565b915050602083015184820360208601526118e38282611873565b915050604083015184820360408601526118fd8282611873565b9150508091505092915050565b600061191683836118ac565b905092915050565b6000602082019050919050565b600061193682611801565b611940818561180c565b9350836020820285016119528561181d565b8060005b8581101561198e578484038952815161196f858261190a565b945061197a8361191e565b925060208a01995050600181019050611956565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b6119e1816119cc565b82525050565b60006119f383836119d8565b60208301905092915050565b6000602082019050919050565b6000611a17826119a0565b611a2181856119ab565b9350611a2c836119bc565b8060005b83811015611a5d578151611a4488826119e7565b9750611a4f836119ff565b925050600181019050611a30565b5085935050505092915050565b60006040820190508181036000830152611a84818561192b565b90508181036020830152611a988184611a0c565b90509392505050565b611aaa8161140a565b8114611ab557600080fd5b50565b600081359050611ac781611aa1565b92915050565b60008060408385031215611ae457611ae3611448565b5b6000611af285828601611ab8565b9250506020611b0385828601611ab8565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000611b458383611873565b905092915050565b6000602082019050919050565b6000611b6582611b0d565b611b6f8185611b18565b935083602082028501611b8185611b29565b8060005b85811015611bbd5784840389528151611b9e8582611b39565b9450611ba983611b4d565b925060208a01995050600181019050611b85565b50829750879550505050505092915050565b60006060820190508181036000830152611be98186611b5a565b90508181036020830152611bfd818561192b565b9050611c0c6040830184611414565b949350505050565b600060208284031215611c2a57611c29611448565b5b600082013567ffffffffffffffff811115611c4857611c4761144d565b5b611c548482850161156a565b91505092915050565b611c66816119cc565b82525050565b6000602082019050611c816000830184611c5d565b92915050565b60006040820190508181036000830152611ca18185611b5a565b9050611cb06020830184611414565b9392505050565b60006060830160008301518482036000860152611cd48282611873565b91505060208301518482036020860152611cee8282611873565b91505060408301518482036040860152611d088282611873565b9150508091505092915050565b60006040820190508181036000830152611d2f8185611cb7565b9050611d3e6020830184611c5d565b9392505050565b600082825260208201905092915050565b7f4b65792063616e6e6f7420626520656d70747900000000000000000000000000600082015250565b6000611d8c601383611d45565b9150611d9782611d56565b602082019050919050565b60006020820190508181036000830152611dbb81611d7f565b9050919050565b600081905092915050565b6000611dd88261182d565b611de28185611dc2565b9350611df2818560208601611849565b80840191505092915050565b6000611e0a8284611dcd565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680611e5c57607f821691505b602082108103611e6f57611e6e611e15565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302611ed77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82611e9a565b611ee18683611e9a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000611f1e611f19611f148461140a565b611ef9565b61140a565b9050919050565b6000819050919050565b611f3883611f03565b611f4c611f4482611f25565b848454611ea7565b825550505050565b600090565b611f61611f54565b611f6c818484611f2f565b505050565b5b81811015611f9057611f85600082611f59565b600181019050611f72565b5050565b601f821115611fd557611fa681611e75565b611faf84611e8a565b81016020851015611fbe578190505b611fd2611fca85611e8a565b830182611f71565b50505b505050565b600082821c905092915050565b6000611ff860001984600802611fda565b1980831691505092915050565b60006120118383611fe7565b9150826002028217905092915050565b61202a8261182d565b67ffffffffffffffff8111156120435761204261146d565b5b61204d8254611e44565b612058828285611f94565b600060209050601f83116001811461208b5760008415612079578287015190505b6120838582612005565b8655506120eb565b601f19841661209986611e75565b60005b828110156120c15784890151825560018201915060208501945060208101905061209c565b868310156120de57848901516120da601f891682611fe7565b8355505b6001600288020188555050505b505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061215c8261140a565b91506121678361140a565b925082820390508181111561217f5761217e612122565b5b92915050565b60006121908261140a565b915061219b8361140a565b92508282019050808211156121b3576121b2612122565b5b92915050565b7f4b657920646f6573206e6f742065786973740000000000000000000000000000600082015250565b60006121ef601283611d45565b91506121fa826121b9565b602082019050919050565b6000602082019050818103600083015261221e816121e2565b9050919050565b6000815461223281611e44565b61223c8186611dc2565b94506001821660008114612257576001811461226c5761229f565b60ff198316865281151582028601935061229f565b61227585611e75565b60005b8381101561229757815481890152600182019150602081019050612278565b838801955050505b50505092915050565b60006122b48284612225565b915081905092915050565b6000815490506122ce81611e44565b9050919050565b60008190508160005260206000209050919050565b8181036122f85750506123d0565b612301826122bf565b67ffffffffffffffff81111561231a5761231961146d565b5b6123248254611e44565b61232f828285611f94565b6000601f83116001811461235e576000841561234c578287015490505b6123568582612005565b8655506123c9565b601f19841661236c876122d5565b965061237786611e75565b60005b8281101561239f5784890154825560018201915060018501945060208101905061237a565b868310156123bc57848901546123b8601f891682611fe7565b8355505b6001600288020188555050505b5050505050505b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea26469706673582212204b40ecf4db330d4b4846e1801fcbe82f88d48e72aca826e80879a5f928ee75b164736f6c63430008180033" diff --git a/systemcontracts/namespace_storage.go b/systemcontracts/namespace_storage.go new file mode 100644 index 0000000000..89b68f4476 --- /dev/null +++ b/systemcontracts/namespace_storage.go @@ -0,0 +1,680 @@ +package systemcontracts + +import ( + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +// NamespaceGenericValue represents the value structure in the NamespaceStorage contract +// This is the same as GenericValue but renamed to avoid conflicts +type NamespaceGenericValue struct { + PrimaryData []byte `json:"primaryData"` + SecondaryData []byte `json:"secondaryData"` + AuxiliaryData []byte `json:"auxiliaryData"` +} + +// NamespaceBatchGetResult represents the result of a batch get operation +type NamespaceBatchGetResult struct { + Values []NamespaceGenericValue `json:"values"` + ExistsFlags []bool `json:"existsFlags"` +} + +// NamespaceListResult represents the result of a list operation +type NamespaceListResult struct { + KeyList [][]byte `json:"keyList"` + Values []NamespaceGenericValue `json:"values"` + Total *big.Int `json:"total"` +} + +// NamespaceListKeysResult represents the result of a listKeys operation +type NamespaceListKeysResult struct { + KeyList [][]byte `json:"keyList"` + Total *big.Int `json:"total"` +} + +// NamespaceListNamespacesResult represents the result of a listNamespaces operation +type NamespaceListNamespacesResult struct { + NamespaceList []string `json:"namespaceList"` + Counts []*big.Int `json:"counts"` + Total *big.Int `json:"total"` +} + +// NamespaceGetResult represents the result of a get operation +type NamespaceGetResult struct { + Value NamespaceGenericValue `json:"value"` + KeyExists bool `json:"keyExists"` +} + +// NamespaceStorageContract provides an interface to interact with the NamespaceStorage smart contract +type NamespaceStorageContract struct { + contractAddress common.Address + backend ContractBackend + abi abi.ABI +} + +// NewNamespaceStorageContract creates a new NamespaceStorage contract instance +func NewNamespaceStorageContract(contractAddress common.Address, backend ContractBackend) (*NamespaceStorageContract, error) { + abi, err := abi.JSON(strings.NewReader(NamespaceStorageABI)) + if err != nil { + return nil, errors.Wrap(err, "failed to parse NamespaceStorage ABI") + } + + return &NamespaceStorageContract{ + contractAddress: contractAddress, + backend: backend, + abi: abi, + }, nil +} + +// Address returns the contract address +func (ns *NamespaceStorageContract) Address() common.Address { + return ns.contractAddress +} + +// Put stores data with the given namespace and key +func (ns *NamespaceStorageContract) Put(namespace string, key []byte, value NamespaceGenericValue) error { + // Validate input + if len(namespace) == 0 { + return errors.New("namespace cannot be empty") + } + if len(key) == 0 { + return errors.New("key cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("put", namespace, key, value) + if err != nil { + return errors.Wrap(err, "failed to pack put call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := ns.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute put") + } + + log.L().Debug("Successfully stored data", + zap.String("namespace", namespace), + zap.String("key", string(key))) + + return nil +} + +// Get retrieves data by namespace and key +func (ns *NamespaceStorageContract) Get(namespace string, key []byte) (*NamespaceGetResult, error) { + // Validate input + if len(namespace) == 0 { + return nil, errors.New("namespace cannot be empty") + } + if len(key) == 0 { + return nil, errors.New("key cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("get", namespace, key) + if err != nil { + return nil, errors.Wrap(err, "failed to pack get call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call get") + } + + // Unpack the result + var getResult struct { + Value NamespaceGenericValue + KeyExists bool + } + + err = ns.abi.UnpackIntoInterface(&getResult, "get", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack get result") + } + + log.L().Debug("Successfully retrieved data", + zap.String("namespace", namespace), + zap.String("key", string(key)), + zap.Bool("exists", getResult.KeyExists)) + + return &NamespaceGetResult{ + Value: getResult.Value, + KeyExists: getResult.KeyExists, + }, nil +} + +// Remove deletes data by namespace and key +func (ns *NamespaceStorageContract) Remove(namespace string, key []byte) error { + // Validate input + if len(namespace) == 0 { + return errors.New("namespace cannot be empty") + } + if len(key) == 0 { + return errors.New("key cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("remove", namespace, key) + if err != nil { + return errors.Wrap(err, "failed to pack remove call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := ns.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute remove") + } + + log.L().Debug("Successfully removed data", + zap.String("namespace", namespace), + zap.String("key", string(key))) + + return nil +} + +// Exists checks if a key exists in a namespace +func (ns *NamespaceStorageContract) Exists(namespace string, key []byte) (bool, error) { + // Validate input + if len(namespace) == 0 { + return false, errors.New("namespace cannot be empty") + } + if len(key) == 0 { + return false, errors.New("key cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("exists", namespace, key) + if err != nil { + return false, errors.Wrap(err, "failed to pack exists call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return false, errors.Wrap(err, "failed to call exists") + } + + // Unpack the result + var keyExists bool + err = ns.abi.UnpackIntoInterface(&keyExists, "exists", result) + if err != nil { + return false, errors.Wrap(err, "failed to unpack exists result") + } + + return keyExists, nil +} + +// BatchGet retrieves multiple values by their keys within a namespace +func (ns *NamespaceStorageContract) BatchGet(namespace string, keys [][]byte) (*NamespaceBatchGetResult, error) { + // Validate input + if len(namespace) == 0 { + return nil, errors.New("namespace cannot be empty") + } + if len(keys) == 0 { + return &NamespaceBatchGetResult{ + Values: []NamespaceGenericValue{}, + ExistsFlags: []bool{}, + }, nil + } + + // Pack the function call + data, err := ns.abi.Pack("batchGet", namespace, keys) + if err != nil { + return nil, errors.Wrap(err, "failed to pack batchGet call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call batchGet") + } + + // Unpack the result + var batchResult struct { + Values []NamespaceGenericValue + ExistsFlags []bool + } + + err = ns.abi.UnpackIntoInterface(&batchResult, "batchGet", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack batchGet result") + } + + log.L().Debug("Successfully batch retrieved data", + zap.String("namespace", namespace), + zap.Int("count", len(keys))) + + return &NamespaceBatchGetResult{ + Values: batchResult.Values, + ExistsFlags: batchResult.ExistsFlags, + }, nil +} + +// BatchPut stores multiple key-value pairs in the same namespace +func (ns *NamespaceStorageContract) BatchPut(namespace string, keys [][]byte, values []NamespaceGenericValue) error { + // Validate input + if len(namespace) == 0 { + return errors.New("namespace cannot be empty") + } + if len(keys) != len(values) { + return errors.New("keys and values arrays must have same length") + } + if len(keys) == 0 { + return nil // Nothing to do + } + + // Pack the function call + data, err := ns.abi.Pack("batchPut", namespace, keys, values) + if err != nil { + return errors.Wrap(err, "failed to pack batchPut call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := ns.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute batchPut") + } + + log.L().Debug("Successfully batch stored data", + zap.String("namespace", namespace), + zap.Int("count", len(keys))) + + return nil +} + +// List retrieves all stored data in a namespace with pagination +func (ns *NamespaceStorageContract) List(namespace string, offset, limit *big.Int) (*NamespaceListResult, error) { + // Validate input + if len(namespace) == 0 { + return nil, errors.New("namespace cannot be empty") + } + if offset == nil { + offset = big.NewInt(0) + } + if limit == nil { + limit = big.NewInt(100) // Default limit + } + + // Pack the function call + data, err := ns.abi.Pack("list", namespace, offset, limit) + if err != nil { + return nil, errors.Wrap(err, "failed to pack list call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call list") + } + + // Unpack the result + var listResult struct { + KeyList [][]byte + Values []NamespaceGenericValue + Total *big.Int + } + + err = ns.abi.UnpackIntoInterface(&listResult, "list", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack list result") + } + + log.L().Debug("Successfully listed data", + zap.String("namespace", namespace), + zap.String("total", listResult.Total.String())) + + return &NamespaceListResult{ + KeyList: listResult.KeyList, + Values: listResult.Values, + Total: listResult.Total, + }, nil +} + +// ListKeys retrieves all keys in a namespace with pagination +func (ns *NamespaceStorageContract) ListKeys(namespace string, offset, limit *big.Int) (*NamespaceListKeysResult, error) { + // Validate input + if len(namespace) == 0 { + return nil, errors.New("namespace cannot be empty") + } + if offset == nil { + offset = big.NewInt(0) + } + if limit == nil { + limit = big.NewInt(100) // Default limit + } + + // Pack the function call + data, err := ns.abi.Pack("listKeys", namespace, offset, limit) + if err != nil { + return nil, errors.Wrap(err, "failed to pack listKeys call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call listKeys") + } + + // Unpack the result + var listKeysResult struct { + KeyList [][]byte + Total *big.Int + } + + err = ns.abi.UnpackIntoInterface(&listKeysResult, "listKeys", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack listKeys result") + } + + log.L().Debug("Successfully listed keys", + zap.String("namespace", namespace), + zap.String("total", listKeysResult.Total.String())) + + return &NamespaceListKeysResult{ + KeyList: listKeysResult.KeyList, + Total: listKeysResult.Total, + }, nil +} + +// ListNamespaces retrieves all namespaces with pagination +func (ns *NamespaceStorageContract) ListNamespaces(offset, limit *big.Int) (*NamespaceListNamespacesResult, error) { + if offset == nil { + offset = big.NewInt(0) + } + if limit == nil { + limit = big.NewInt(100) // Default limit + } + + // Pack the function call + data, err := ns.abi.Pack("listNamespaces", offset, limit) + if err != nil { + return nil, errors.Wrap(err, "failed to pack listNamespaces call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call listNamespaces") + } + + // Unpack the result + var listNamespacesResult struct { + NamespaceList []string + Counts []*big.Int + Total *big.Int + } + + err = ns.abi.UnpackIntoInterface(&listNamespacesResult, "listNamespaces", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack listNamespaces result") + } + + log.L().Debug("Successfully listed namespaces", + zap.String("total", listNamespacesResult.Total.String())) + + return &NamespaceListNamespacesResult{ + NamespaceList: listNamespacesResult.NamespaceList, + Counts: listNamespacesResult.Counts, + Total: listNamespacesResult.Total, + }, nil +} + +// HasNamespace checks if a namespace exists +func (ns *NamespaceStorageContract) HasNamespace(namespace string) (bool, error) { + // Validate input + if len(namespace) == 0 { + return false, errors.New("namespace cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("hasNamespace", namespace) + if err != nil { + return false, errors.Wrap(err, "failed to pack hasNamespace call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return false, errors.Wrap(err, "failed to call hasNamespace") + } + + // Unpack the result + var nsExists bool + err = ns.abi.UnpackIntoInterface(&nsExists, "hasNamespace", result) + if err != nil { + return false, errors.Wrap(err, "failed to unpack hasNamespace result") + } + + return nsExists, nil +} + +// CountInNamespace returns the number of items in a namespace +func (ns *NamespaceStorageContract) CountInNamespace(namespace string) (*big.Int, error) { + // Validate input + if len(namespace) == 0 { + return nil, errors.New("namespace cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("countInNamespace", namespace) + if err != nil { + return nil, errors.Wrap(err, "failed to pack countInNamespace call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call countInNamespace") + } + + // Unpack the result + var itemCount *big.Int + err = ns.abi.UnpackIntoInterface(&itemCount, "countInNamespace", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack countInNamespace result") + } + + return itemCount, nil +} + +// NamespaceCount returns the total number of namespaces +func (ns *NamespaceStorageContract) NamespaceCount() (*big.Int, error) { + // Pack the function call + data, err := ns.abi.Pack("namespaceCount") + if err != nil { + return nil, errors.Wrap(err, "failed to pack namespaceCount call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call namespaceCount") + } + + // Unpack the result + var totalNamespaces *big.Int + err = ns.abi.UnpackIntoInterface(&totalNamespaces, "namespaceCount", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack namespaceCount result") + } + + return totalNamespaces, nil +} + +// TotalCount returns the total number of items across all namespaces +func (ns *NamespaceStorageContract) TotalCount() (*big.Int, error) { + // Pack the function call + data, err := ns.abi.Pack("totalCount") + if err != nil { + return nil, errors.Wrap(err, "failed to pack totalCount call") + } + + // Execute the call + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + result, err := ns.backend.Call(callMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to call totalCount") + } + + // Unpack the result + var totalItems *big.Int + err = ns.abi.UnpackIntoInterface(&totalItems, "totalCount", result) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack totalCount result") + } + + return totalItems, nil +} + +// ClearNamespace clears all data in a specific namespace +func (ns *NamespaceStorageContract) ClearNamespace(namespace string) error { + // Validate input + if len(namespace) == 0 { + return errors.New("namespace cannot be empty") + } + + // Pack the function call + data, err := ns.abi.Pack("clearNamespace", namespace) + if err != nil { + return errors.Wrap(err, "failed to pack clearNamespace call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := ns.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute clearNamespace") + } + + log.L().Debug("Successfully cleared namespace", + zap.String("namespace", namespace)) + + return nil +} + +// ClearAll clears all stored data across all namespaces (emergency function) +func (ns *NamespaceStorageContract) ClearAll() error { + // Pack the function call + data, err := ns.abi.Pack("clearAll") + if err != nil { + return errors.Wrap(err, "failed to pack clearAll call") + } + + // Execute the transaction + callMsg := ðereum.CallMsg{ + To: &ns.contractAddress, + Data: data, + Value: big.NewInt(0), + Gas: math.MaxUint64, + } + + if err := ns.backend.Handle(callMsg); err != nil { + return errors.Wrap(err, "failed to execute clearAll") + } + + log.L().Debug("Successfully cleared all data") + + return nil +} diff --git a/systemcontracts/namespace_storage_abi.go b/systemcontracts/namespace_storage_abi.go new file mode 100644 index 0000000000..da1745c18e --- /dev/null +++ b/systemcontracts/namespace_storage_abi.go @@ -0,0 +1,506 @@ +package systemcontracts + +// NamespaceStorageABI is the ABI definition for the NamespaceStorage contract +const NamespaceStorageABI = `[ + { + "anonymous": false, + "inputs": [], + "name": "AllDataCleared", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "keyCount", + "type": "uint256" + } + ], + "name": "BatchDataRetrieved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "DataDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "DataStored", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "namespace", + "type": "string" + } + ], + "name": "NamespaceCleared", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + } + ], + "name": "batchGet", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct NamespaceStorage.GenericValue[]", + "name": "values", + "type": "tuple[]" + }, + { + "internalType": "bool[]", + "name": "existsFlags", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes[]", + "name": "keys", + "type": "bytes[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct NamespaceStorage.GenericValue[]", + "name": "values", + "type": "tuple[]" + } + ], + "name": "batchPut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "clearAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + } + ], + "name": "clearNamespace", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + } + ], + "name": "countInNamespace", + "outputs": [ + { + "internalType": "uint256", + "name": "itemCount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "exists", + "outputs": [ + { + "internalType": "bool", + "name": "keyExists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "get", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct NamespaceStorage.GenericValue", + "name": "value", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "keyExists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + } + ], + "name": "hasNamespace", + "outputs": [ + { + "internalType": "bool", + "name": "nsExists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "list", + "outputs": [ + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct NamespaceStorage.GenericValue[]", + "name": "values", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "listKeys", + "outputs": [ + { + "internalType": "bytes[]", + "name": "keyList", + "type": "bytes[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "listNamespaces", + "outputs": [ + { + "internalType": "string[]", + "name": "namespaceList", + "type": "string[]" + }, + { + "internalType": "uint256[]", + "name": "counts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "namespaceCount", + "outputs": [ + { + "internalType": "uint256", + "name": "totalNamespaces", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "primaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "secondaryData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "auxiliaryData", + "type": "bytes" + } + ], + "internalType": "struct NamespaceStorage.GenericValue", + "name": "value", + "type": "tuple" + } + ], + "name": "put", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "namespace", + "type": "string" + }, + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "remove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalCount", + "outputs": [ + { + "internalType": "uint256", + "name": "totalItems", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +]` + +// NamespaceStorageContractByteCodeStr is the bytecode for the NamespaceStorage contract +const NamespaceStorageContractByteCodeStr = "608060405234801561001057600080fd5b50613df1806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c8063912f309511610097578063c9037d2d11610066578063c9037d2d146102b1578063d1e96865146102e1578063ebb689a114610311578063f53504231461031b576100f5565b8063912f309514610214578063a2bc7a1c14610246578063aff6274514610262578063b40ec02b14610293576100f5565b80634b6b9a8c116100d35780634b6b9a8c146101655780634e8bfabb1461019657806353df2b27146101b257806370eafd18146101e4576100f5565b806304e52369146100fa57806312862f7f1461011657806334eafb1114610147575b600080fd5b610114600480360381019061010f91906126b3565b610337565b005b610130600480360381019061012b9190612811565b61064f565b60405161013e929190612af2565b60405180910390f35b61014f6109f1565b60405161015c9190612b42565b60405180910390f35b61017f600480360381019061017a9190612b89565b610a63565b60405161018d929190612cba565b60405180910390f35b6101b060048036038101906101ab9190612e8d565b610c54565b005b6101cc60048036038101906101c79190612f34565b61105b565b6040516101db93929190613149565b60405180910390f35b6101fe60048036038101906101f9919061318e565b611309565b60405161020b91906131e6565b60405180910390f35b61022e60048036038101906102299190612b89565b61133e565b60405161023d93929190613201565b60405180910390f35b610260600480360381019061025b919061318e565b6117fd565b005b61027c600480360381019061027791906126b3565b611a82565b60405161028a9291906132a4565b60405180910390f35b61029b611ce1565b6040516102a89190612b42565b60405180910390f35b6102cb60048036038101906102c6919061318e565b611cee565b6040516102d89190612b42565b60405180910390f35b6102fb60048036038101906102f691906126b3565b611d19565b60405161030891906131e6565b60405180910390f35b610319611d6b565b005b610335600480360381019061033091906132d4565b61207d565b005b60048260405161034791906133b7565b908152602001604051809103902060009054906101000a900460ff166103a2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103999061342b565b60405180910390fd5b6002826040516103b291906133b7565b9081526020016040518091039020816040516103ce9190613487565b908152602001604051809103902060009054906101000a900460ff16610429576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610420906134ea565b60405180910390fd5b60008260405161043991906133b7565b9081526020016040518091039020816040516104559190613487565b9081526020016040518091039020600080820160006104749190612370565b6001820160006104849190612370565b6002820160006104949190612370565b505060006002836040516104a891906133b7565b9081526020016040518091039020826040516104c49190613487565b908152602001604051809103902060006101000a81548160ff02191690831515021790555060006001836040516104fb91906133b7565b9081526020016040518091039020905060005b81805490508110156105f15782805190602001208282815481106105355761053461350a565b5b9060005260206000200160405161054c9190613631565b6040518091039020036105e457816001838054905061056b9190613677565b8154811061057c5761057b61350a565b5b906000526020600020018282815481106105995761059861350a565b5b9060005260206000200190816105af919061386d565b50818054806105c1576105c0613955565b5b6001900381819060005260206000200160006105dd9190612370565b90556105f1565b808060010191505061050e565b50816040516106009190613487565b60405180910390208360405161061691906133b7565b60405180910390207faf8967c7b21227d5d987c31cd064433b027cf5bd93330cdbe6b62909093c417260405160405180910390a3505050565b606080825167ffffffffffffffff81111561066d5761066c6124e7565b5b6040519080825280602002602001820160405280156106a657816020015b6106936123b0565b81526020019060019003908161068b5790505b509150825167ffffffffffffffff8111156106c4576106c36124e7565b5b6040519080825280602002602001820160405280156106f25781602001602082028036833780820191505090505b50905060005b83518110156109e95760028560405161071191906133b7565b90815260200160405180910390208482815181106107325761073161350a565b5b60200260200101516040516107479190613487565b908152602001604051809103902060009054906101000a900460ff168282815181106107765761077561350a565b5b60200260200101901515908115158152505081818151811061079b5761079a61350a565b5b6020026020010151156109dc576000856040516107b891906133b7565b90815260200160405180910390208482815181106107d9576107d861350a565b5b60200260200101516040516107ee9190613487565b908152602001604051809103902060405180606001604052908160008201805461081790613568565b80601f016020809104026020016040519081016040528092919081815260200182805461084390613568565b80156108905780601f1061086557610100808354040283529160200191610890565b820191906000526020600020905b81548152906001019060200180831161087357829003601f168201915b505050505081526020016001820180546108a990613568565b80601f01602080910402602001604051908101604052809291908181526020018280546108d590613568565b80156109225780601f106108f757610100808354040283529160200191610922565b820191906000526020600020905b81548152906001019060200180831161090557829003601f168201915b5050505050815260200160028201805461093b90613568565b80601f016020809104026020016040519081016040528092919081815260200182805461096790613568565b80156109b45780601f10610989576101008083540402835291602001916109b4565b820191906000526020600020905b81548152906001019060200180831161099757829003601f168201915b5050505050815250508382815181106109d0576109cf61350a565b5b60200260200101819052505b80806001019150506106f8565b509250929050565b600080600090505b600380549050811015610a5f57600160038281548110610a1c57610a1b61350a565b5b90600052602060002001604051610a339190613a1c565b90815260200160405180910390208054905082610a509190613a33565b915080806001019150506109f9565b5090565b6060600080600186604051610a7891906133b7565b9081526020016040518091039020905080805490509150818510610aed57600067ffffffffffffffff811115610ab157610ab06124e7565b5b604051908082528060200260200182016040528015610ae457816020015b6060815260200190600190039081610acf5790505b50925050610c4c565b60008583610afb9190613677565b90506000818611610b0c5785610b0e565b815b90508067ffffffffffffffff811115610b2a57610b296124e7565b5b604051908082528060200260200182016040528015610b5d57816020015b6060815260200190600190039081610b485790505b50945060005b81811015610c4757838189610b789190613a33565b81548110610b8957610b8861350a565b5b906000526020600020018054610b9e90613568565b80601f0160208091040260200160405190810160405280929190818152602001828054610bca90613568565b8015610c175780601f10610bec57610100808354040283529160200191610c17565b820191906000526020600020905b815481529060010190602001808311610bfa57829003601f168201915b5050505050868281518110610c2f57610c2e61350a565b5b60200260200101819052508080600101915050610b63565b505050505b935093915050565b8051825114610c98576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c8f90613ad9565b60405180910390fd5b6000835111610cdc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cd390613b45565b60405180910390fd5b600483604051610cec91906133b7565b908152602001604051809103902060009054906101000a900460ff16610d7957600383908060018154018082558091505060019003906000526020600020016000909190919091509081610d409190613bab565b506001600484604051610d5391906133b7565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b60005b8251811015611055576000838281518110610d9a57610d9961350a565b5b60200260200101515111610de3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dda90613cc9565b60405180910390fd5b600284604051610df391906133b7565b9081526020016040518091039020838281518110610e1457610e1361350a565b5b6020026020010151604051610e299190613487565b908152602001604051809103902060009054906101000a900460ff16610f2257600184604051610e5991906133b7565b9081526020016040518091039020838281518110610e7a57610e7961350a565b5b6020026020010151908060018154018082558091505060019003906000526020600020016000909190919091509081610eb39190613ce9565b506001600285604051610ec691906133b7565b9081526020016040518091039020848381518110610ee757610ee661350a565b5b6020026020010151604051610efc9190613487565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b818181518110610f3557610f3461350a565b5b6020026020010151600085604051610f4d91906133b7565b9081526020016040518091039020848381518110610f6e57610f6d61350a565b5b6020026020010151604051610f839190613487565b90815260200160405180910390206000820151816000019081610fa69190613ce9565b506020820151816001019081610fbc9190613ce9565b506040820151816002019081610fd29190613ce9565b50905050828181518110610fe957610fe861350a565b5b6020026020010151604051610ffe9190613487565b60405180910390208460405161101491906133b7565b60405180910390207fd00871f87cd1b1b4e50e5be511f060478c06a013856673c392f2adbfa65df8ad60405160405180910390a38080600101915050610d7c565b50505050565b6060806000600380549050905080851061111157600067ffffffffffffffff81111561108a576110896124e7565b5b6040519080825280602002602001820160405280156110bd57816020015b60608152602001906001900390816110a85790505b509250600067ffffffffffffffff8111156110db576110da6124e7565b5b6040519080825280602002602001820160405280156111095781602001602082028036833780820191505090505b509150611302565b6000858261111f9190613677565b905060008186116111305785611132565b815b90508067ffffffffffffffff81111561114e5761114d6124e7565b5b60405190808252806020026020018201604052801561118157816020015b606081526020019060019003908161116c5790505b5094508067ffffffffffffffff81111561119e5761119d6124e7565b5b6040519080825280602002602001820160405280156111cc5781602001602082028036833780820191505090505b50935060005b818110156112fe5760006003828a6111ea9190613a33565b815481106111fb576111fa61350a565b5b90600052602060002001805461121090613568565b80601f016020809104026020016040519081016040528092919081815260200182805461123c90613568565b80156112895780601f1061125e57610100808354040283529160200191611289565b820191906000526020600020905b81548152906001019060200180831161126c57829003601f168201915b50505050509050808783815181106112a4576112a361350a565b5b60200260200101819052506001816040516112bf91906133b7565b9081526020016040518091039020805490508683815181106112e4576112e361350a565b5b6020026020010181815250505080806001019150506111d2565b5050505b9250925092565b600060048260405161131b91906133b7565b908152602001604051809103902060009054906101000a900460ff169050919050565b60608060008060018760405161135491906133b7565b908152602001604051809103902090508080549050915081861061142057600067ffffffffffffffff81111561138d5761138c6124e7565b5b6040519080825280602002602001820160405280156113c057816020015b60608152602001906001900390816113ab5790505b509350600067ffffffffffffffff8111156113de576113dd6124e7565b5b60405190808252806020026020018201604052801561141757816020015b6114046123b0565b8152602001906001900390816113fc5790505b509250506117f4565b6000868361142e9190613677565b9050600081871161143f5786611441565b815b90508067ffffffffffffffff81111561145d5761145c6124e7565b5b60405190808252806020026020018201604052801561149057816020015b606081526020019060019003908161147b5790505b5095508067ffffffffffffffff8111156114ad576114ac6124e7565b5b6040519080825280602002602001820160405280156114e657816020015b6114d36123b0565b8152602001906001900390816114cb5790505b50945060005b818110156117ef57600084828b6115039190613a33565b815481106115145761151361350a565b5b90600052602060002001805461152990613568565b80601f016020809104026020016040519081016040528092919081815260200182805461155590613568565b80156115a25780601f10611577576101008083540402835291602001916115a2565b820191906000526020600020905b81548152906001019060200180831161158557829003601f168201915b50505050509050808883815181106115bd576115bc61350a565b5b602002602001018190525060008b6040516115d891906133b7565b9081526020016040518091039020816040516115f49190613487565b908152602001604051809103902060405180606001604052908160008201805461161d90613568565b80601f016020809104026020016040519081016040528092919081815260200182805461164990613568565b80156116965780601f1061166b57610100808354040283529160200191611696565b820191906000526020600020905b81548152906001019060200180831161167957829003601f168201915b505050505081526020016001820180546116af90613568565b80601f01602080910402602001604051908101604052809291908181526020018280546116db90613568565b80156117285780601f106116fd57610100808354040283529160200191611728565b820191906000526020600020905b81548152906001019060200180831161170b57829003601f168201915b5050505050815260200160028201805461174190613568565b80601f016020809104026020016040519081016040528092919081815260200182805461176d90613568565b80156117ba5780601f1061178f576101008083540402835291602001916117ba565b820191906000526020600020905b81548152906001019060200180831161179d57829003601f168201915b5050505050815250508783815181106117d6576117d561350a565b5b60200260200101819052505080806001019150506114ec565b505050505b93509350939050565b60048160405161180d91906133b7565b908152602001604051809103902060009054906101000a900460ff16611868576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161185f9061342b565b60405180910390fd5b600060018260405161187a91906133b7565b9081526020016040518091039020905060005b8180549050811015611a115760008282815481106118ae576118ad61350a565b5b9060005260206000200180546118c390613568565b80601f01602080910402602001604051908101604052809291908181526020018280546118ef90613568565b801561193c5780601f106119115761010080835404028352916020019161193c565b820191906000526020600020905b81548152906001019060200180831161191f57829003601f168201915b5050505050905060008460405161195391906133b7565b90815260200160405180910390208160405161196f9190613487565b90815260200160405180910390206000808201600061198e9190612370565b60018201600061199e9190612370565b6002820160006119ae9190612370565b505060006002856040516119c291906133b7565b9081526020016040518091039020826040516119de9190613487565b908152602001604051809103902060006101000a81548160ff02191690831515021790555050808060010191505061188d565b50600182604051611a2291906133b7565b90815260200160405180910390206000611a3c91906123d1565b81604051611a4a91906133b7565b60405180910390207f372dd188f66e03080f62fc207ebf4954a98c81128ca2472aedfc273f0f7c788360405160405180910390a25050565b611a8a6123b0565b6000600284604051611a9c91906133b7565b908152602001604051809103902083604051611ab89190613487565b908152602001604051809103902060009054906101000a900460ff1690508015611cda57600084604051611aec91906133b7565b908152602001604051809103902083604051611b089190613487565b9081526020016040518091039020604051806060016040529081600082018054611b3190613568565b80601f0160208091040260200160405190810160405280929190818152602001828054611b5d90613568565b8015611baa5780601f10611b7f57610100808354040283529160200191611baa565b820191906000526020600020905b815481529060010190602001808311611b8d57829003601f168201915b50505050508152602001600182018054611bc390613568565b80601f0160208091040260200160405190810160405280929190818152602001828054611bef90613568565b8015611c3c5780601f10611c1157610100808354040283529160200191611c3c565b820191906000526020600020905b815481529060010190602001808311611c1f57829003601f168201915b50505050508152602001600282018054611c5590613568565b80601f0160208091040260200160405190810160405280929190818152602001828054611c8190613568565b8015611cce5780601f10611ca357610100808354040283529160200191611cce565b820191906000526020600020905b815481529060010190602001808311611cb157829003601f168201915b50505050508152505091505b9250929050565b6000600380549050905090565b6000600182604051611d0091906133b7565b9081526020016040518091039020805490509050919050565b6000600283604051611d2b91906133b7565b908152602001604051809103902082604051611d479190613487565b908152602001604051809103902060009054906101000a900460ff16905092915050565b60005b60038054905081101561204057600060038281548110611d9157611d9061350a565b5b906000526020600020018054611da690613568565b80601f0160208091040260200160405190810160405280929190818152602001828054611dd290613568565b8015611e1f5780601f10611df457610100808354040283529160200191611e1f565b820191906000526020600020905b815481529060010190602001808311611e0257829003601f168201915b505050505090506000600182604051611e3891906133b7565b9081526020016040518091039020905060005b8180549050811015611fcf576000828281548110611e6c57611e6b61350a565b5b906000526020600020018054611e8190613568565b80601f0160208091040260200160405190810160405280929190818152602001828054611ead90613568565b8015611efa5780601f10611ecf57610100808354040283529160200191611efa565b820191906000526020600020905b815481529060010190602001808311611edd57829003601f168201915b50505050509050600084604051611f1191906133b7565b908152602001604051809103902081604051611f2d9190613487565b908152602001604051809103902060008082016000611f4c9190612370565b600182016000611f5c9190612370565b600282016000611f6c9190612370565b50506000600285604051611f8091906133b7565b908152602001604051809103902082604051611f9c9190613487565b908152602001604051809103902060006101000a81548160ff021916908315150217905550508080600101915050611e4b565b50600182604051611fe091906133b7565b90815260200160405180910390206000611ffa91906123d1565b600060048360405161200c91906133b7565b908152602001604051809103902060006101000a81548160ff02191690831515021790555050508080600101915050611d6e565b506003600061204f91906123f2565b7f30addec5e9e954699f286bae8e1558655b0d436c3d7b501014eb5c0b4a76e6db60405160405180910390a1565b60008351116120c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120b890613b45565b60405180910390fd5b6000825111612105576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120fc90613cc9565b60405180910390fd5b60048360405161211591906133b7565b908152602001604051809103902060009054906101000a900460ff166121a2576003839080600181540180825580915050600190039060005260206000200160009091909190915090816121699190613bab565b50600160048460405161217c91906133b7565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b6002836040516121b291906133b7565b9081526020016040518091039020826040516121ce9190613487565b908152602001604051809103902060009054906101000a900460ff16612293576001836040516121fe91906133b7565b90815260200160405180910390208290806001815401808255809150506001900390600052602060002001600090919091909150908161223e9190613ce9565b50600160028460405161225191906133b7565b90815260200160405180910390208360405161226d9190613487565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b806000846040516122a491906133b7565b9081526020016040518091039020836040516122c09190613487565b908152602001604051809103902060008201518160000190816122e39190613ce9565b5060208201518160010190816122f99190613ce9565b50604082015181600201908161230f9190613ce9565b50905050816040516123219190613487565b60405180910390208360405161233791906133b7565b60405180910390207fd00871f87cd1b1b4e50e5be511f060478c06a013856673c392f2adbfa65df8ad60405160405180910390a3505050565b50805461237c90613568565b6000825580601f1061238e57506123ad565b601f0160209004906000526020600020908101906123ac9190612413565b5b50565b60405180606001604052806060815260200160608152602001606081525090565b50805460008255906000526020600020908101906123ef9190612430565b50565b50805460008255906000526020600020908101906124109190612454565b50565b5b8082111561242c576000816000905550600101612414565b5090565b5b8082111561245057600081816124479190612370565b50600101612431565b5090565b5b80821115612474576000818161246b9190612478565b50600101612455565b5090565b50805461248490613568565b6000825580601f1061249657506124b5565b601f0160209004906000526020600020908101906124b49190612413565b5b50565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61251f826124d6565b810181811067ffffffffffffffff8211171561253e5761253d6124e7565b5b80604052505050565b60006125516124b8565b905061255d8282612516565b919050565b600067ffffffffffffffff82111561257d5761257c6124e7565b5b612586826124d6565b9050602081019050919050565b82818337600083830152505050565b60006125b56125b084612562565b612547565b9050828152602081018484840111156125d1576125d06124d1565b5b6125dc848285612593565b509392505050565b600082601f8301126125f9576125f86124cc565b5b81356126098482602086016125a2565b91505092915050565b600067ffffffffffffffff82111561262d5761262c6124e7565b5b612636826124d6565b9050602081019050919050565b600061265661265184612612565b612547565b905082815260208101848484011115612672576126716124d1565b5b61267d848285612593565b509392505050565b600082601f83011261269a576126996124cc565b5b81356126aa848260208601612643565b91505092915050565b600080604083850312156126ca576126c96124c2565b5b600083013567ffffffffffffffff8111156126e8576126e76124c7565b5b6126f4858286016125e4565b925050602083013567ffffffffffffffff811115612715576127146124c7565b5b61272185828601612685565b9150509250929050565b600067ffffffffffffffff821115612746576127456124e7565b5b602082029050602081019050919050565b600080fd5b600061276f61276a8461272b565b612547565b9050808382526020820190506020840283018581111561279257612791612757565b5b835b818110156127d957803567ffffffffffffffff8111156127b7576127b66124cc565b5b8086016127c48982612685565b85526020850194505050602081019050612794565b5050509392505050565b600082601f8301126127f8576127f76124cc565b5b813561280884826020860161275c565b91505092915050565b60008060408385031215612828576128276124c2565b5b600083013567ffffffffffffffff811115612846576128456124c7565b5b612852858286016125e4565b925050602083013567ffffffffffffffff811115612873576128726124c7565b5b61287f858286016127e3565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b838110156128ef5780820151818401526020810190506128d4565b60008484015250505050565b6000612906826128b5565b61291081856128c0565b93506129208185602086016128d1565b612929816124d6565b840191505092915050565b6000606083016000830151848203600086015261295182826128fb565b9150506020830151848203602086015261296b82826128fb565b9150506040830151848203604086015261298582826128fb565b9150508091505092915050565b600061299e8383612934565b905092915050565b6000602082019050919050565b60006129be82612889565b6129c88185612894565b9350836020820285016129da856128a5565b8060005b85811015612a1657848403895281516129f78582612992565b9450612a02836129a6565b925060208a019950506001810190506129de565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b612a6981612a54565b82525050565b6000612a7b8383612a60565b60208301905092915050565b6000602082019050919050565b6000612a9f82612a28565b612aa98185612a33565b9350612ab483612a44565b8060005b83811015612ae5578151612acc8882612a6f565b9750612ad783612a87565b925050600181019050612ab8565b5085935050505092915050565b60006040820190508181036000830152612b0c81856129b3565b90508181036020830152612b208184612a94565b90509392505050565b6000819050919050565b612b3c81612b29565b82525050565b6000602082019050612b576000830184612b33565b92915050565b612b6681612b29565b8114612b7157600080fd5b50565b600081359050612b8381612b5d565b92915050565b600080600060608486031215612ba257612ba16124c2565b5b600084013567ffffffffffffffff811115612bc057612bbf6124c7565b5b612bcc868287016125e4565b9350506020612bdd86828701612b74565b9250506040612bee86828701612b74565b9150509250925092565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000612c3083836128fb565b905092915050565b6000602082019050919050565b6000612c5082612bf8565b612c5a8185612c03565b935083602082028501612c6c85612c14565b8060005b85811015612ca85784840389528151612c898582612c24565b9450612c9483612c38565b925060208a01995050600181019050612c70565b50829750879550505050505092915050565b60006040820190508181036000830152612cd48185612c45565b9050612ce36020830184612b33565b9392505050565b600067ffffffffffffffff821115612d0557612d046124e7565b5b602082029050602081019050919050565b600080fd5b600080fd5b600060608284031215612d3657612d35612d16565b5b612d406060612547565b9050600082013567ffffffffffffffff811115612d6057612d5f612d1b565b5b612d6c84828501612685565b600083015250602082013567ffffffffffffffff811115612d9057612d8f612d1b565b5b612d9c84828501612685565b602083015250604082013567ffffffffffffffff811115612dc057612dbf612d1b565b5b612dcc84828501612685565b60408301525092915050565b6000612deb612de684612cea565b612547565b90508083825260208201905060208402830185811115612e0e57612e0d612757565b5b835b81811015612e5557803567ffffffffffffffff811115612e3357612e326124cc565b5b808601612e408982612d20565b85526020850194505050602081019050612e10565b5050509392505050565b600082601f830112612e7457612e736124cc565b5b8135612e84848260208601612dd8565b91505092915050565b600080600060608486031215612ea657612ea56124c2565b5b600084013567ffffffffffffffff811115612ec457612ec36124c7565b5b612ed0868287016125e4565b935050602084013567ffffffffffffffff811115612ef157612ef06124c7565b5b612efd868287016127e3565b925050604084013567ffffffffffffffff811115612f1e57612f1d6124c7565b5b612f2a86828701612e5f565b9150509250925092565b60008060408385031215612f4b57612f4a6124c2565b5b6000612f5985828601612b74565b9250506020612f6a85828601612b74565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b6000612fc782612fa0565b612fd18185612fab565b9350612fe18185602086016128d1565b612fea816124d6565b840191505092915050565b60006130018383612fbc565b905092915050565b6000602082019050919050565b600061302182612f74565b61302b8185612f7f565b93508360208202850161303d85612f90565b8060005b85811015613079578484038952815161305a8582612ff5565b945061306583613009565b925060208a01995050600181019050613041565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6130c081612b29565b82525050565b60006130d283836130b7565b60208301905092915050565b6000602082019050919050565b60006130f68261308b565b6131008185613096565b935061310b836130a7565b8060005b8381101561313c57815161312388826130c6565b975061312e836130de565b92505060018101905061310f565b5085935050505092915050565b600060608201905081810360008301526131638186613016565b9050818103602083015261317781856130eb565b90506131866040830184612b33565b949350505050565b6000602082840312156131a4576131a36124c2565b5b600082013567ffffffffffffffff8111156131c2576131c16124c7565b5b6131ce848285016125e4565b91505092915050565b6131e081612a54565b82525050565b60006020820190506131fb60008301846131d7565b92915050565b6000606082019050818103600083015261321b8186612c45565b9050818103602083015261322f81856129b3565b905061323e6040830184612b33565b949350505050565b6000606083016000830151848203600086015261326382826128fb565b9150506020830151848203602086015261327d82826128fb565b9150506040830151848203604086015261329782826128fb565b9150508091505092915050565b600060408201905081810360008301526132be8185613246565b90506132cd60208301846131d7565b9392505050565b6000806000606084860312156132ed576132ec6124c2565b5b600084013567ffffffffffffffff81111561330b5761330a6124c7565b5b613317868287016125e4565b935050602084013567ffffffffffffffff811115613338576133376124c7565b5b61334486828701612685565b925050604084013567ffffffffffffffff811115613365576133646124c7565b5b61337186828701612d20565b9150509250925092565b600081905092915050565b600061339182612fa0565b61339b818561337b565b93506133ab8185602086016128d1565b80840191505092915050565b60006133c38284613386565b915081905092915050565b600082825260208201905092915050565b7f4e616d65737061636520646f6573206e6f742065786973740000000000000000600082015250565b60006134156018836133ce565b9150613420826133df565b602082019050919050565b6000602082019050818103600083015261344481613408565b9050919050565b600081905092915050565b6000613461826128b5565b61346b818561344b565b935061347b8185602086016128d1565b80840191505092915050565b60006134938284613456565b915081905092915050565b7f4b657920646f6573206e6f7420657869737420696e206e616d65737061636500600082015250565b60006134d4601f836133ce565b91506134df8261349e565b602082019050919050565b60006020820190508181036000830152613503816134c7565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061358057607f821691505b60208210810361359357613592613539565b5b50919050565b60008190508160005260206000209050919050565b600081546135bb81613568565b6135c5818661344b565b945060018216600081146135e057600181146135f557613628565b60ff1983168652811515820286019350613628565b6135fe85613599565b60005b8381101561362057815481890152600182019150602081019050613601565b838801955050505b50505092915050565b600061363d82846135ae565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061368282612b29565b915061368d83612b29565b92508282039050818111156136a5576136a4613648565b5b92915050565b6000815490506136ba81613568565b9050919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026137237fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826136e6565b61372d86836136e6565b95508019841693508086168417925050509392505050565b6000819050919050565b600061376a61376561376084612b29565b613745565b612b29565b9050919050565b6000819050919050565b6137848361374f565b61379861379082613771565b8484546136f3565b825550505050565b600090565b6137ad6137a0565b6137b881848461377b565b505050565b5b818110156137dc576137d16000826137a5565b6001810190506137be565b5050565b601f821115613821576137f281613599565b6137fb846136d6565b8101602085101561380a578190505b61381e613816856136d6565b8301826137bd565b50505b505050565b600082821c905092915050565b600061384460001984600802613826565b1980831691505092915050565b600061385d8383613833565b9150826002028217905092915050565b81810361387b575050613953565b613884826136ab565b67ffffffffffffffff81111561389d5761389c6124e7565b5b6138a78254613568565b6138b28282856137e0565b6000601f8311600181146138e157600084156138cf578287015490505b6138d98582613851565b86555061394c565b601f1984166138ef876136c1565b96506138fa86613599565b60005b82811015613922578489015482556001820191506001850194506020810190506138fd565b8683101561393f578489015461393b601f891682613833565b8355505b6001600288020188555050505b5050505050505b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b60008190508160005260206000209050919050565b600081546139a681613568565b6139b0818661337b565b945060018216600081146139cb57600181146139e057613a13565b60ff1983168652811515820286019350613a13565b6139e985613984565b60005b83811015613a0b578154818901526001820191506020810190506139ec565b838801955050505b50505092915050565b6000613a288284613999565b915081905092915050565b6000613a3e82612b29565b9150613a4983612b29565b9250828201905080821115613a6157613a60613648565b5b92915050565b7f4b65797320616e642076616c75657320617272617973206d757374206861766560008201527f2073616d65206c656e6774680000000000000000000000000000000000000000602082015250565b6000613ac3602c836133ce565b9150613ace82613a67565b604082019050919050565b60006020820190508181036000830152613af281613ab6565b9050919050565b7f4e616d6573706163652063616e6e6f7420626520656d70747900000000000000600082015250565b6000613b2f6019836133ce565b9150613b3a82613af9565b602082019050919050565b60006020820190508181036000830152613b5e81613b22565b9050919050565b601f821115613ba657613b7781613984565b613b80846136d6565b81016020851015613b8f578190505b613ba3613b9b856136d6565b8301826137bd565b50505b505050565b613bb482612fa0565b67ffffffffffffffff811115613bcd57613bcc6124e7565b5b613bd78254613568565b613be2828285613b65565b600060209050601f831160018114613c155760008415613c03578287015190505b613c0d8582613851565b865550613c75565b601f198416613c2386613984565b60005b82811015613c4b57848901518255600182019150602085019450602081019050613c26565b86831015613c685784890151613c64601f891682613833565b8355505b6001600288020188555050505b505050505050565b7f4b65792063616e6e6f7420626520656d70747900000000000000000000000000600082015250565b6000613cb36013836133ce565b9150613cbe82613c7d565b602082019050919050565b60006020820190508181036000830152613ce281613ca6565b9050919050565b613cf2826128b5565b67ffffffffffffffff811115613d0b57613d0a6124e7565b5b613d158254613568565b613d208282856137e0565b600060209050601f831160018114613d535760008415613d41578287015190505b613d4b8582613851565b865550613db3565b601f198416613d6186613599565b60005b82811015613d8957848901518255600182019150602085019450602081019050613d64565b86831015613da65784890151613da2601f891682613833565b8355505b6001600288020188555050505b50505050505056fea264697066735822122010e6a669447af75331d35ed4f9d0505114fdea36140b19387ecd7764e89d732064736f6c63430008180033" diff --git a/systemcontracts/systemcontracts.go b/systemcontracts/systemcontracts.go new file mode 100644 index 0000000000..df83219409 --- /dev/null +++ b/systemcontracts/systemcontracts.go @@ -0,0 +1,141 @@ +// Package systemcontracts provides system contract management functionality +package systemcontracts + +import ( + "encoding/hex" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +// SystemContract represents a system contract with its address and bytecode +type SystemContract struct { + Address address.Address + Code []byte +} + +const ( + // StakingBucketsContractIndex is the system contract for staking buckets storage + StakingBucketsContractIndex int = iota + // BucketPoolContractIndex is the system contract for bucket pool storage + BucketPoolContractIndex + // BucketIndicesContractIndex is the system contract for bucket indices storage + BucketIndicesContractIndex + // EndorsementContractIndex is the system contract for endorsement storage + EndorsementContractIndex + // CandidateMapContractIndex is the system contract for candidate map storage + CandidateMapContractIndex + // CandidatesContractIndex is the system contract for candidates storage + CandidatesContractIndex + // PollCandidateListContractIndex is the system contract for poll candidate storage + PollCandidateListContractIndex + // PollLegacyCandidateListContractIndex is the system contract for poll legacy candidate storage + PollLegacyCandidateListContractIndex + // PollProbationListContractIndex is the system contract for poll probation list storage + PollProbationListContractIndex + // PollUnproductiveDelegateContractIndex is the system contract for poll unproductive delegate storage + PollUnproductiveDelegateContractIndex + // PollBlockMetaContractIndex is the system contract for poll block meta storage + PollBlockMetaContractIndex + // RewardingContractV1Index is the system contract for rewarding admin storage + RewardingContractV1Index + // RewardingContractV2Index is the system contract for rewarding admin storage v2 + RewardingContractV2Index + // StakingViewContractIndex is the system contract for staking view storage + StakingViewContractIndex + // SystemContractCount is the total number of system contracts + SystemContractCount +) + +const ( + defaultSystemContractType = iota + namespaceStorageContractType +) + +var systemContractTypes = map[int]int{ + StakingViewContractIndex: namespaceStorageContractType, +} + +// SystemContracts holds all system contracts +var SystemContracts []SystemContract + +var systemContractCreatorAddr = hash.Hash160b([]byte("system_contract_creator")) + +func init() { + initSystemContracts() +} + +// initSystemContracts initializes the system contracts array +func initSystemContracts() { + genericStorageByteCode, err := hex.DecodeString(GenericStorageByteCodeStr) + if err != nil { + log.S().Panic("failed to decode GenericStorageByteCode: " + err.Error()) + } + namespaceStorageByteCode, err := hex.DecodeString(NamespaceStorageContractByteCodeStr) + if err != nil { + log.S().Panic("failed to decode NamespaceStorageContractByteCode: " + err.Error()) + } + + SystemContracts = make([]SystemContract, SystemContractCount) + for i := 0; i < SystemContractCount; i++ { + addr, err := address.FromBytes(crypto.CreateAddress(common.BytesToAddress(systemContractCreatorAddr[:]), uint64(i)).Bytes()) + if err != nil { + log.S().Panic("Invalid system contract address: " + err.Error()) + } + var byteCode []byte + switch systemContractTypes[i] { + case namespaceStorageContractType: + byteCode = namespaceStorageByteCode + default: + byteCode = genericStorageByteCode + } + SystemContracts[i] = SystemContract{ + Address: addr, + Code: byteCode, + } + } +} + +// ContractBackend defines the interface for contract backend operations +type ContractBackend interface { + Call(callMsg *ethereum.CallMsg) ([]byte, error) + Handle(callMsg *ethereum.CallMsg) error +} + +// ContractDeployer defines the interface for contract deployment operations +type ContractDeployer interface { + Deploy(callMsg *ethereum.CallMsg) (address.Address, error) + Exists(addr address.Address) bool +} + +// DeploySystemContractsIfNotExist deploys system contracts if they don't exist +func DeploySystemContractsIfNotExist(deployer ContractDeployer) error { + for idx, contract := range SystemContracts { + exists := deployer.Exists(contract.Address) + if !exists { + log.S().Infof("Deploying system contract [%d] %s", idx, contract.Address.String()) + msg := ðereum.CallMsg{ + From: common.BytesToAddress(systemContractCreatorAddr[:]), + Data: contract.Code, + Value: big.NewInt(0), + Gas: 10000000, + } + if addr, err := deployer.Deploy(msg); err != nil { + return fmt.Errorf("failed to deploy system contract %s: %w", contract.Address.String(), err) + } else if addr.String() != contract.Address.String() { + return fmt.Errorf("deployed contract address %s does not match expected address %s", addr.String(), contract.Address.String()) + } + log.S().Infof("System contract [%d] %s deployed successfully", idx, contract.Address.String()) + } else { + log.S().Infof("System contract [%d] %s already exists", idx, contract.Address.String()) + } + } + return nil +} From b5c792217b0e0c66f58fd356e1fe5d54f9f4e3ad Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 09:09:52 +0800 Subject: [PATCH 02/34] define state namespaces --- action/protocol/execution/evm/contract.go | 6 +-- action/protocol/protocol.go | 3 +- action/protocol/rewarding/protocol.go | 2 +- action/protocol/staking/protocol.go | 6 +-- state/factory/factory.go | 9 +--- state/tables.go | 61 +++++++++++++++++++++++ 6 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 state/tables.go diff --git a/action/protocol/execution/evm/contract.go b/action/protocol/execution/evm/contract.go index bca8a17e4a..6b0a74a39d 100644 --- a/action/protocol/execution/evm/contract.go +++ b/action/protocol/execution/evm/contract.go @@ -20,11 +20,11 @@ import ( const ( // CodeKVNameSpace is the bucket name for code - CodeKVNameSpace = "Code" + CodeKVNameSpace = state.CodeKVNameSpace // ContractKVNameSpace is the bucket name for contract data storage - ContractKVNameSpace = "Contract" + ContractKVNameSpace = state.ContractKVNameSpace // PreimageKVNameSpace is the bucket name for preimage data storage - PreimageKVNameSpace = "Preimage" + PreimageKVNameSpace = state.PreimageKVNameSpace ) type ( diff --git a/action/protocol/protocol.go b/action/protocol/protocol.go index 39607e1928..7d8d4f4cca 100644 --- a/action/protocol/protocol.go +++ b/action/protocol/protocol.go @@ -16,6 +16,7 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/state" ) var ( @@ -27,7 +28,7 @@ var ( const ( // SystemNamespace is the namespace to store system information such as candidates/probationList/unproductiveDelegates - SystemNamespace = "System" + SystemNamespace = state.SystemNamespace ) // Protocol defines the protocol interfaces atop IoTeX blockchain diff --git a/action/protocol/rewarding/protocol.go b/action/protocol/rewarding/protocol.go index 258026a731..8375a45ace 100644 --- a/action/protocol/rewarding/protocol.go +++ b/action/protocol/rewarding/protocol.go @@ -28,7 +28,7 @@ import ( const ( // TODO: it works only for one instance per protocol definition now _protocolID = "rewarding" - _v2RewardingNamespace = "Rewarding" + _v2RewardingNamespace = state.RewardingNamespace ) var ( diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index cb82fd60aa..59d510651d 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -34,13 +34,13 @@ const ( _protocolID = "staking" // _stakingNameSpace is the bucket name for staking state - _stakingNameSpace = "Staking" + _stakingNameSpace = state.StakingNamespace // _candidateNameSpace is the bucket name for candidate state - _candidateNameSpace = "Candidate" + _candidateNameSpace = state.CandidateNamespace // CandsMapNS is the bucket name to store candidate map - CandsMapNS = "CandsMap" + CandsMapNS = state.CandsMapNamespace ) const ( diff --git a/state/factory/factory.go b/state/factory/factory.go index 44c75aa807..ffc913a331 100644 --- a/state/factory/factory.go +++ b/state/factory/factory.go @@ -26,19 +26,14 @@ import ( "github.com/iotexproject/iotex-core/v2/db/trie" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" + "github.com/iotexproject/iotex-core/v2/state" ) const ( // AccountKVNamespace is the bucket name for account - AccountKVNamespace = "Account" - // ArchiveNamespacePrefix is the prefix of the buckets storing history data - ArchiveNamespacePrefix = "Archive" + AccountKVNamespace = state.AccountKVNamespace // CurrentHeightKey indicates the key of current factory height in underlying DB CurrentHeightKey = "currentHeight" - // ArchiveTrieNamespace is the bucket for the latest state view - ArchiveTrieNamespace = "AccountTrie" - // ArchiveTrieRootKey indicates the key of accountTrie root hash in underlying DB - ArchiveTrieRootKey = "archiveTrieRoot" ) var ( diff --git a/state/tables.go b/state/tables.go new file mode 100644 index 0000000000..97e9486d84 --- /dev/null +++ b/state/tables.go @@ -0,0 +1,61 @@ +package state + +const ( + // SystemNamespace is the namespace to store system information such as candidates/probationList/unproductiveDelegates + // Poll Protocol uses this namespace to store states: + // - hash256(CurCandidateKey/NextCandidateKey) --> CandidatesList + // - hash256(CurProbationKey/NextProbationKey) --> ProbationList + // - hash256(UnproductiveDelegatesKey) --> UnproductiveDelegates + // - hash256(BlockMetaPrefix)+height%heightInEpoch --> BlockMeta + SystemNamespace = "System" + + // AccountKVNamespace is the bucket name for account + // Poll Protocol uses this namespace to store LEGACY states: + // - hash160(CandidatesPrefix+height) --> CandidatesList + // Rewarding Protocol uses this namespace to store LEGACY states: + // - hash160(hash160(rewarding)+adminKey) --> admin + // - hash160(hash160(rewarding)+exemptKey) --> exempt + // - hash160(hash160(rewarding)+fundKey) --> fund + // - hash160(hash160(rewarding)+_blockRewardHistoryKeyPrefix+height) --> rewardHistory + // - hash160(hash160(rewarding)+_epochRewardHistoryKeyPrefix+epoch) --> rewardHistory + // - hash160(hash160(rewarding)+adminKey+address) --> rewardAccount + AccountKVNamespace = "Account" + + // RewardingNamespace is the namespace to store rewarding information + // - hash160(rewarding)+adminKey --> admin + // - hash160(rewarding)+exemptKey --> exempt + // - hash160(rewarding)+fundKey --> fund + // - hash160(rewarding)+_blockRewardHistoryKeyPrefix+height --> rewardHistory + // - hash160(rewarding)+_epochRewardHistoryKeyPrefix+epoch --> rewardHistory + // - hash160(rewarding)+adminKey+address --> rewardAccount + RewardingNamespace = "Rewarding" + + // StakingNamespace is the namespace to store staking information + // - "0" + totalBucketKey --> totalBucketCount + // - "1" + --> VoteBucket + // - "2" + --> BucketIndices + // - "3" + --> BucketIndices + // - "4" + --> Endorsement + StakingNamespace = "Staking" + + // CandidateNamespace is the namespace to store candidate information + // - --> Candidate + CandidateNamespace = "Candidate" + + // CandsMapNamespace is the namespace to store candidate map + // - "name" --> CandidateList + // - "operator" --> CandidateList + // - "owner" --> CandidateList + CandsMapNamespace = "CandsMap" + + // CodeKVNameSpace is the bucket name for code + // codeHash --> code + CodeKVNameSpace = "Code" + + // ContractKVNameSpace is the bucket name for contract data storage + // trieKey --> trieValue + ContractKVNameSpace = "Contract" + + // PreimageKVNameSpace is the bucket name for preimage data storage + PreimageKVNameSpace = "Preimage" +) From 4e9288a99463f98f8b400d70072cd654e2c800a2 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 09:14:38 +0800 Subject: [PATCH 03/34] contract backend --- action/protocol/execution/evm/evm.go | 20 +- state/factory/contract_backend.go | 272 +++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 state/factory/contract_backend.go diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go index 4623d49e82..08da1c89aa 100644 --- a/action/protocol/execution/evm/evm.go +++ b/action/protocol/execution/evm/evm.go @@ -8,6 +8,7 @@ package evm import ( "bytes" "context" + "encoding/hex" "math" "math/big" "time" @@ -326,10 +327,7 @@ func ExecuteContract( if ps.featureCtx.SetRevertMessageToReceipt && receipt.Status == uint64(iotextypes.ReceiptStatus_ErrExecutionReverted) && retval != nil && bytes.Equal(retval[:4], _revertSelector) { // in case of the execution revert error, parse the retVal and add to receipt - data := retval[4:] - msgLength := byteutil.BytesToUint64BigEndian(data[56:64]) - revertMsg := string(data[64 : 64+msgLength]) - receipt.SetExecutionRevertMsg(revertMsg) + receipt.SetExecutionRevertMsg(ExtractRevertMessage(retval)) } log.S().Debugf("Retval: %x, Receipt: %+v, %v", retval, receipt, err) if tCtx, ok := GetTracerCtx(ctx); ok && tCtx.CaptureTx != nil { @@ -765,3 +763,17 @@ func SimulateExecution( )) return ExecuteContract(ctx, sm, ex) } + +// ExtractRevertMessage extracts the revert message from the return value +func ExtractRevertMessage(ret []byte) string { + if len(ret) < 4 { + return hex.EncodeToString(ret) + } + if !bytes.Equal(ret[:4], _revertSelector) { + return hex.EncodeToString(ret) + } + data := ret[4:] + msgLength := byteutil.BytesToUint64BigEndian(data[56:64]) + revertMsg := string(data[64 : 64+msgLength]) + return revertMsg +} diff --git a/state/factory/contract_backend.go b/state/factory/contract_backend.go new file mode 100644 index 0000000000..a6ce741daf --- /dev/null +++ b/state/factory/contract_backend.go @@ -0,0 +1,272 @@ +package factory + +import ( + "context" + "encoding/hex" + "math" + "math/big" + "time" + + "github.com/erigontech/erigon-lib/chain" + erigonComm "github.com/erigontech/erigon-lib/common" + erigonstate "github.com/erigontech/erigon/core/state" + erigonAcc "github.com/erigontech/erigon/core/types/accounts" + "github.com/erigontech/erigon/core/vm" + "github.com/erigontech/erigon/core/vm/evmtypes" + "github.com/ethereum/go-ethereum" + "github.com/holiman/uint256" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" + iotexevm "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/pkg/log" +) + +type ( + contractBacked struct { + intraBlockState *erigonstate.IntraBlockState + org erigonstate.StateReader + + // helper fields + height uint64 + timestamp time.Time + g *genesis.Genesis + evmNetworkID uint32 + } +) + +func NewContractBackend(intraBlockState *erigonstate.IntraBlockState, org erigonstate.StateReader, height uint64, timestamp time.Time, g *genesis.Genesis, evmNetworkID uint32) *contractBacked { + return &contractBacked{ + intraBlockState: intraBlockState, + org: org, + height: height, + timestamp: timestamp, + g: g, + evmNetworkID: evmNetworkID, + } +} + +func (backend *contractBacked) Call(callMsg *ethereum.CallMsg) ([]byte, error) { + return backend.call(callMsg, erigonstate.New(&intraStateReader{backend.intraBlockState, backend.org})) +} + +func (backend *contractBacked) Handle(callMsg *ethereum.CallMsg) error { + _, err := backend.call(callMsg, backend.intraBlockState) + return err +} + +func (backend *contractBacked) Deploy(callMsg *ethereum.CallMsg) (address.Address, error) { + evm, err := backend.prepare(backend.intraBlockState) + if err != nil { + return nil, errors.Wrap(err, "failed to prepare EVM for contract deployment") + } + ret, addr, leftGas, err := evm.Create(vm.AccountRef(callMsg.From), callMsg.Data, callMsg.Gas, uint256.MustFromBig(callMsg.Value), true) + if err != nil { + if errors.Is(err, vm.ErrExecutionReverted) { + revertMsg := iotexevm.ExtractRevertMessage(ret) + log.L().Error("EVM deployment reverted", + zap.String("from", callMsg.From.String()), + zap.String("data", hex.EncodeToString(callMsg.Data)), + zap.String("revertMessage", revertMsg), + zap.String("returnData", hex.EncodeToString(ret)), + ) + return nil, errors.Wrapf(err, "deployment reverted: %s", revertMsg) + } + return nil, errors.Wrap(err, "failed to deploy contract") + } + log.L().Info("EVM deployment result", + zap.String("from", callMsg.From.String()), + zap.String("data", hex.EncodeToString(callMsg.Data)), + zap.String("ret", hex.EncodeToString(ret)), + zap.String("address", addr.String()), + zap.Uint64("gasLeft", leftGas), + ) + + return address.FromBytes(addr.Bytes()) +} + +func (backend *contractBacked) Exists(addr address.Address) bool { + return backend.intraBlockState.Exist(erigonComm.BytesToAddress(addr.Bytes())) +} + +func (backend *contractBacked) prepare(intra evmtypes.IntraBlockState) (*vm.EVM, error) { + + // deploy system contracts + blkCtxE := evmtypes.BlockContext{ + CanTransfer: func(state evmtypes.IntraBlockState, addr erigonComm.Address, amount *uint256.Int) bool { + log.L().Debug("CanTransfer called in erigon genesis state creation", + zap.String("address", addr.String()), + zap.String("amount", amount.String()), + ) + return true + }, + Transfer: func(state evmtypes.IntraBlockState, from erigonComm.Address, to erigonComm.Address, amount *uint256.Int, bailout bool) { + log.L().Debug("Transfer called in erigon genesis state creation", + zap.String("from", from.String()), + zap.String("to", to.String()), + zap.String("amount", amount.String()), + ) + return + }, + GetHash: func(block uint64) erigonComm.Hash { + log.L().Debug("GetHash called in erigon genesis state creation", + zap.Uint64("block", block), + ) + return erigonComm.Hash{} + }, + PostApplyMessage: func(ibs evmtypes.IntraBlockState, sender erigonComm.Address, coinbase erigonComm.Address, result *evmtypes.ExecutionResult) { + log.L().Debug("PostApplyMessage called in erigon genesis state creation", + zap.String("sender", sender.String()), + zap.String("coinbase", coinbase.String()), + ) + return + }, + Coinbase: erigonComm.Address{}, + GasLimit: math.MaxUint64, + MaxGasLimit: true, + BlockNumber: backend.height, + Time: uint64(backend.timestamp.Unix()), + Difficulty: big.NewInt(50), + BaseFee: nil, + PrevRanDao: nil, + BlobBaseFee: nil, + } + txCtxE := evmtypes.TxContext{ + TxHash: erigonComm.Hash{}, + Origin: erigonComm.Address{}, + GasPrice: uint256.NewInt(0), + BlobFee: nil, + BlobHashes: nil, + } + ctx := protocol.WithBlockCtx(context.Background(), protocol.BlockCtx{ + BlockHeight: backend.height, + BlockTimeStamp: backend.timestamp}) + ctx = genesis.WithGenesisContext(ctx, *backend.g) + ctx = protocol.WithBlockchainCtx(ctx, protocol.BlockchainCtx{ + GetBlockTime: func(u uint64) (time.Time, error) { + interval := 2500 * time.Millisecond + return backend.timestamp.Add(interval * time.Duration(u-backend.height)), nil + }, + EvmNetworkID: backend.evmNetworkID, + }) + chainCfg, err := evm.NewChainConfig(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to create chain config") + } + var ( + shanghaiTime *big.Int + cancunTime *big.Int + ) + if chainCfg.ShanghaiTime != nil { + shanghaiTime = big.NewInt(int64(*chainCfg.ShanghaiTime)) + } + if chainCfg.CancunTime != nil { + cancunTime = big.NewInt(int64(*chainCfg.CancunTime)) + } + chainConfig := &chain.Config{ + HomesteadBlock: chainCfg.ConstantinopleBlock, + DAOForkBlock: chainCfg.ConstantinopleBlock, + TangerineWhistleBlock: chainCfg.ConstantinopleBlock, + SpuriousDragonBlock: chainCfg.ConstantinopleBlock, + ByzantiumBlock: chainCfg.ConstantinopleBlock, + ConstantinopleBlock: chainCfg.ConstantinopleBlock, + PetersburgBlock: chainCfg.PetersburgBlock, + IstanbulBlock: chainCfg.IstanbulBlock, + MuirGlacierBlock: chainCfg.MuirGlacierBlock, + BerlinBlock: chainCfg.BerlinBlock, + LondonBlock: chainCfg.LondonBlock, + ArrowGlacierBlock: chainCfg.ArrowGlacierBlock, + GrayGlacierBlock: chainCfg.GrayGlacierBlock, + + ShanghaiTime: shanghaiTime, + CancunTime: cancunTime, + } + vmConfig := vm.Config{ + NoBaseFee: true, + } + evm := vm.NewEVM(blkCtxE, txCtxE, intra, chainConfig, vmConfig) + return evm, nil +} + +func (backend *contractBacked) call(callMsg *ethereum.CallMsg, intra evmtypes.IntraBlockState) ([]byte, error) { + evm, err := backend.prepare(intra) + if err != nil { + return nil, errors.Wrap(err, "failed to prepare EVM for contract call") + } + t := time.Now() + ret, gasLeft, err := evm.Call(vm.AccountRef(callMsg.From), erigonComm.Address(*callMsg.To), callMsg.Data, callMsg.Gas, uint256.MustFromBig(callMsg.Value), true) + if err != nil { + // Check if it's a revert error and extract the revert message + if errors.Is(err, vm.ErrExecutionReverted) { + revertMsg := iotexevm.ExtractRevertMessage(ret) + log.L().Error("EVM call reverted", + zap.String("from", callMsg.From.String()), + zap.String("to", callMsg.To.String()), + zap.Uint64("dataSize", uint64(len(callMsg.Data))), + zap.String("revertMessage", revertMsg), + zap.String("returnData", hex.EncodeToString(ret)), + ) + return ret, errors.Wrapf(err, "execution reverted: %s", revertMsg) + } + return ret, errors.Wrapf(err, "error when system contract %x action mutates states", callMsg.To.Bytes()) + } + log.L().Info("EVM call result", + zap.String("from", callMsg.From.String()), + zap.String("to", callMsg.To.String()), + zap.Uint64("dataSize", uint64(len(callMsg.Data))), + zap.String("ret", hex.EncodeToString(ret)), + zap.Uint64("gasUsed", callMsg.Gas-gasLeft), + zap.Duration("duration", time.Since(t)), + ) + return ret, nil +} + +type intraStateReader struct { + intra *erigonstate.IntraBlockState + org erigonstate.StateReader +} + +func (sr *intraStateReader) ReadAccountData(address erigonComm.Address) (*erigonAcc.Account, error) { + org, err := sr.org.ReadAccountData(address) + if err != nil { + return nil, errors.Wrapf(err, "failed to read account data for address %s", address.String()) + } + acc := &erigonAcc.Account{ + Initialised: false, + Nonce: sr.intra.GetNonce(address), + Balance: *sr.intra.GetBalance(address), + Root: erigonComm.Hash{}, + CodeHash: sr.intra.GetCodeHash(address), + Incarnation: sr.intra.GetIncarnation(address), + PrevIncarnation: 0, + } + if org != nil { + acc.Initialised = org.Initialised + acc.Root = org.Root + acc.PrevIncarnation = org.PrevIncarnation + } + return acc, nil +} + +func (sr *intraStateReader) ReadAccountStorage(address erigonComm.Address, incarnation uint64, key *erigonComm.Hash) ([]byte, error) { + value := new(uint256.Int) + sr.intra.GetState(address, key, value) + return value.Bytes(), nil +} + +func (sr *intraStateReader) ReadAccountCode(address erigonComm.Address, incarnation uint64, codeHash erigonComm.Hash) ([]byte, error) { + code := sr.intra.GetCode(address) + return code, nil +} +func (sr *intraStateReader) ReadAccountCodeSize(address erigonComm.Address, incarnation uint64, codeHash erigonComm.Hash) (int, error) { + return len(sr.intra.GetCode(address)), nil +} + +func (sr *intraStateReader) ReadAccountIncarnation(address erigonComm.Address) (uint64, error) { + return sr.intra.GetIncarnation(address), nil +} From e37f09ee33f267e5ef47916b1d94b2e94b3b52d2 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 09:59:08 +0800 Subject: [PATCH 04/34] contract storage --- state/contract_storage.go | 26 ++++++ state/contract_storage_namespaced.go | 133 +++++++++++++++++++++++++++ state/contract_storage_standard.go | 127 +++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 state/contract_storage.go create mode 100644 state/contract_storage_namespaced.go create mode 100644 state/contract_storage_standard.go diff --git a/state/contract_storage.go b/state/contract_storage.go new file mode 100644 index 0000000000..889e95c74e --- /dev/null +++ b/state/contract_storage.go @@ -0,0 +1,26 @@ +package state + +import ( + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type ContractStorage interface { + StoreToContract(ns string, key []byte, backend systemcontracts.ContractBackend) error + LoadFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error + DeleteFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error + ListFromContract(ns string, backend systemcontracts.ContractBackend) ([][]byte, []any, error) + BatchFromContract(ns string, keys [][]byte, backend systemcontracts.ContractBackend) ([]any, error) +} + +type ContractStorageStandard interface { + ContractStorageAddress(ns string, key []byte) (address.Address, error) + New() ContractStorageStandard + Serialize() ([]byte, error) + Deserialize([]byte) error +} + +type ContractStorageProxy interface { + ContractStorageProxy() ContractStorage +} diff --git a/state/contract_storage_namespaced.go b/state/contract_storage_namespaced.go new file mode 100644 index 0000000000..f244dc0390 --- /dev/null +++ b/state/contract_storage_namespaced.go @@ -0,0 +1,133 @@ +package state + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type contractStorageNamespacedWrapper struct { + standard ContractStorageStandard +} + +// NewContractStorageNamespacedWrapper creates a new ContractStorage wrapper for namespaced storage +func NewContractStorageNamespacedWrapper(standard ContractStorageStandard) ContractStorage { + return &contractStorageNamespacedWrapper{standard: standard} +} + +func (cs *contractStorageNamespacedWrapper) StoreToContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + data, err := cs.standard.Serialize() + if err != nil { + return errors.Wrap(err, "failed to serialize storage standard") + } + if err := contract.Put(ns, key, systemcontracts.NamespaceGenericValue{PrimaryData: data}); err != nil { + return errors.Wrapf(err, "failed to store storage standard to contract %s", contract.Address().Hex()) + } + log.S().Infof("Stored storage standard to contract %s with key %x, value: %+v", contract.Address().Hex(), key, cs.standard) + return nil +} + +func (cs *contractStorageNamespacedWrapper) LoadFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + value, err := contract.Get(ns, key) + if err != nil { + return errors.Wrapf(err, "failed to get storage standard from contract %s with key %x", contract.Address().Hex(), key) + } + if !value.KeyExists { + return errors.Wrapf(ErrStateNotExist, "storage standard does not exist in contract %s with key %x", contract.Address().Hex(), key) + } + if err := cs.standard.Deserialize(value.Value.PrimaryData); err != nil { + return errors.Wrap(err, "failed to deserialize storage standard") + } + log.S().Infof("Loaded storage standard from contract %s with key %x, value: %+v", contract.Address().Hex(), key, cs.standard) + return nil +} + +func (cs *contractStorageNamespacedWrapper) DeleteFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + if err := contract.Remove(ns, key); err != nil { + return errors.Wrapf(err, "failed to delete storage standard from contract %s with key %x", contract.Address().Hex(), key) + } + log.S().Infof("Deleted storage standard from contract %s with key %x", contract.Address().Hex(), key) + return nil +} + +func (cs *contractStorageNamespacedWrapper) ListFromContract(ns string, backend systemcontracts.ContractBackend) ([][]byte, []any, error) { + contract, err := cs.storageContract(ns, nil, backend) + if err != nil { + return nil, nil, err + } + count, err := contract.CountInNamespace(ns) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to count storage standards in contract %s", contract.Address().Hex()) + } + if count.Sign() == 0 { + return nil, nil, nil + } + listResult, err := contract.List(ns, big.NewInt(0), count) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to list storage standards from contract %s", contract.Address().Hex()) + } + log.S().Infof("Listed storage standards from contract %s with keys %v", contract.Address().Hex(), listResult.KeyList) + var indices []any + for _, value := range listResult.Values { + bi := cs.standard.New() + if err := bi.Deserialize(value.PrimaryData); err != nil { + return nil, nil, errors.Wrapf(err, "failed to deserialize storage standard from contract %s", contract.Address().Hex()) + } + indices = append(indices, bi) + } + log.S().Debugf("Listed %d storage standards from contract %s", len(indices), contract.Address().Hex()) + return listResult.KeyList, indices, nil +} + +func (cs *contractStorageNamespacedWrapper) BatchFromContract(ns string, keys [][]byte, backend systemcontracts.ContractBackend) ([]any, error) { + contract, err := cs.storageContract(ns, nil, backend) + if err != nil { + return nil, err + } + storeResult, err := contract.BatchGet(ns, keys) + if err != nil { + return nil, errors.Wrap(err, "failed to batch get storage standards from contract") + } + results := make([]any, 0, len(storeResult.Values)) + for i, value := range storeResult.Values { + if !storeResult.ExistsFlags[i] { + results = append(results, nil) + continue + } + res := cs.standard.New() + if err := res.Deserialize(value.PrimaryData); err != nil { + return nil, errors.Wrapf(err, "failed to deserialize storage standard %x", keys[i]) + } + results = append(results, res) + } + log.S().Infof("Batch loaded %d storage standard from contract %s with keys %d", len(results), contract.Address().Hex(), len(keys)) + return results, nil +} + +func (cs *contractStorageNamespacedWrapper) storageContract(ns string, key []byte, backend systemcontracts.ContractBackend) (*systemcontracts.NamespaceStorageContract, error) { + addr, err := cs.standard.ContractStorageAddress(ns, key) + if err != nil { + return nil, err + } + contract, err := systemcontracts.NewNamespaceStorageContract(common.BytesToAddress(addr.Bytes()), backend) + if err != nil { + return nil, errors.Wrapf(err, "failed to create block meta storage contract") + } + return contract, nil +} diff --git a/state/contract_storage_standard.go b/state/contract_storage_standard.go new file mode 100644 index 0000000000..b2037b32a0 --- /dev/null +++ b/state/contract_storage_standard.go @@ -0,0 +1,127 @@ +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type contractStorageStandardWrapper struct { + standard ContractStorageStandard +} + +func NewContractStorageStandardWrapper(standard ContractStorageStandard) ContractStorage { + return &contractStorageStandardWrapper{standard: standard} +} + +func (cs *contractStorageStandardWrapper) StoreToContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + data, err := cs.standard.Serialize() + if err != nil { + return errors.Wrap(err, "failed to serialize storage standard") + } + if err := contract.Put(key, systemcontracts.GenericValue{PrimaryData: data}); err != nil { + return errors.Wrapf(err, "failed to store storage standard to contract %s", contract.Address().Hex()) + } + return nil +} + +func (cs *contractStorageStandardWrapper) LoadFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + value, err := contract.Get(key) + if err != nil { + return errors.Wrapf(err, "failed to get storage standard from contract %s with key %x", contract.Address().Hex(), key) + } + if !value.KeyExists { + return errors.Wrapf(ErrStateNotExist, "storage standard does not exist in contract %s with key %x", contract.Address().Hex(), key) + } + if err := cs.standard.Deserialize(value.Value.PrimaryData); err != nil { + return errors.Wrap(err, "failed to deserialize storage standard") + } + return nil +} + +func (cs *contractStorageStandardWrapper) DeleteFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := cs.storageContract(ns, key, backend) + if err != nil { + return err + } + if err := contract.Remove(key); err != nil { + return errors.Wrapf(err, "failed to delete storage standard from contract %s with key %x", contract.Address().Hex(), key) + } + return nil +} + +func (cs *contractStorageStandardWrapper) ListFromContract(ns string, backend systemcontracts.ContractBackend) ([][]byte, []any, error) { + contract, err := cs.storageContract(ns, nil, backend) + if err != nil { + return nil, nil, err + } + count, err := contract.Count() + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to count storage standards in contract %s", contract.Address().Hex()) + } + if count.Sign() == 0 { + return nil, nil, nil + } + listResult, err := contract.List(0, count.Uint64()) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to list storage standards from contract %s", contract.Address().Hex()) + } + log.S().Debugf("Listed storage standards from contract %s with keys %v", contract.Address().Hex(), listResult.KeyList) + var indices []any + for _, value := range listResult.Values { + bi := cs.standard.New() + if err := bi.Deserialize(value.PrimaryData); err != nil { + return nil, nil, errors.Wrapf(err, "failed to deserialize storage standard from contract %s", contract.Address().Hex()) + } + indices = append(indices, bi) + } + log.S().Debugf("Listed %d storage standards from contract %s", len(indices), contract.Address().Hex()) + return listResult.KeyList, indices, nil +} + +func (cs *contractStorageStandardWrapper) BatchFromContract(ns string, keys [][]byte, backend systemcontracts.ContractBackend) ([]any, error) { + contract, err := cs.storageContract(ns, nil, backend) + if err != nil { + return nil, err + } + storeResult, err := contract.BatchGet(keys) + if err != nil { + return nil, errors.Wrap(err, "failed to batch get storage standards from contract") + } + results := make([]any, 0, len(storeResult.Values)) + for i, value := range storeResult.Values { + if !storeResult.ExistsFlags[i] { + results = append(results, nil) + continue + } + res := cs.standard.New() + if err := res.Deserialize(value.PrimaryData); err != nil { + return nil, errors.Wrapf(err, "failed to deserialize storage standard %x", keys[i]) + } + results = append(results, res) + } + log.S().Debugf("Batch loaded %d storage standard from contract %s with keys %d", len(results), contract.Address().Hex(), len(keys)) + return results, nil +} + +func (cs *contractStorageStandardWrapper) storageContract(ns string, key []byte, backend systemcontracts.ContractBackend) (*systemcontracts.GenericStorageContract, error) { + addr, err := cs.standard.ContractStorageAddress(ns, key) + if err != nil { + return nil, err + } + contract, err := systemcontracts.NewGenericStorageContract(common.BytesToAddress(addr.Bytes()), backend) + if err != nil { + return nil, errors.Wrapf(err, "failed to create block meta storage contract") + } + return contract, nil +} From a16e573f09171b948a6f7a50404c976c075d4d88 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 09:59:51 +0800 Subject: [PATCH 05/34] workingset object store --- action/protocol/managers.go | 9 ++ state/factory/workingset.go | 19 ++- state/factory/workingsetstore.go | 44 ++++++- state/factory/workingsetstore_erigon.go | 116 +++++++++++++++++- .../workingsetstore_erigon_simulate.go | 27 +++- state/factory/workingsetstore_test.go | 4 +- .../factory/workingsetstore_with_secondary.go | 35 +++++- 7 files changed, 233 insertions(+), 21 deletions(-) diff --git a/action/protocol/managers.go b/action/protocol/managers.go index edca9796c9..7b24c616c6 100644 --- a/action/protocol/managers.go +++ b/action/protocol/managers.go @@ -54,12 +54,21 @@ func CreateStateConfig(opts ...StateOption) (*StateConfig, error) { return &cfg, nil } +// ObjectOption sets the object for call +func ObjectOption(obj any) StateOption { + return func(cfg *StateConfig) error { + cfg.Object = obj + return nil + } +} + type ( // StateConfig is the config for accessing stateDB StateConfig struct { Namespace string // namespace used by state's storage Key []byte Keys [][]byte + Object any // object used by state's storage } // StateOption sets parameter for access state diff --git a/state/factory/workingset.go b/state/factory/workingset.go index 38b08b12b7..30ca383d5c 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -336,11 +336,7 @@ func (ws *workingSet) State(s interface{}, opts ...protocol.StateOption) (uint64 if cfg.Keys != nil { return 0, errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet") } - value, err := ws.store.Get(cfg.Namespace, cfg.Key) - if err != nil { - return ws.height, err - } - return ws.height, state.Deserialize(s, value) + return ws.height, ws.store.GetObject(cfg.Namespace, cfg.Key, s) } func (ws *workingSet) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) { @@ -351,7 +347,7 @@ func (ws *workingSet) States(opts ...protocol.StateOption) (uint64, state.Iterat if cfg.Key != nil { return 0, nil, errors.Wrap(ErrNotSupported, "Read states with key option has not been implemented yet") } - keys, values, err := ws.store.States(cfg.Namespace, cfg.Keys) + keys, values, err := ws.store.States(cfg.Namespace, cfg.Keys, cfg.Object) if err != nil { return 0, nil, err } @@ -369,11 +365,7 @@ func (ws *workingSet) PutState(s interface{}, opts ...protocol.StateOption) (uin if err != nil { return ws.height, err } - ss, err := state.Serialize(s) - if err != nil { - return ws.height, errors.Wrapf(err, "failed to convert account %v to bytes", s) - } - return ws.height, ws.store.Put(cfg.Namespace, cfg.Key, ss) + return ws.height, ws.store.PutObject(cfg.Namespace, cfg.Key, s) } // DelState deletes a state from DB @@ -383,7 +375,7 @@ func (ws *workingSet) DelState(opts ...protocol.StateOption) (uint64, error) { if err != nil { return ws.height, err } - return ws.height, ws.store.Delete(cfg.Namespace, cfg.Key) + return ws.height, ws.store.DeleteObject(cfg.Namespace, cfg.Key, cfg.Object) } // ReadView reads the view @@ -399,6 +391,9 @@ func (ws *workingSet) WriteView(name string, v protocol.View) error { // CreateGenesisStates initialize the genesis states func (ws *workingSet) CreateGenesisStates(ctx context.Context) error { + if err := ws.store.CreateGenesisStates(ctx); err != nil { + return err + } if reg, ok := protocol.GetRegistry(ctx); ok { for _, p := range reg.All() { if gsc, ok := p.(protocol.GenesisStateCreator); ok { diff --git a/state/factory/workingsetstore.go b/state/factory/workingsetstore.go index 7c2651c781..57b546ee5c 100644 --- a/state/factory/workingsetstore.go +++ b/state/factory/workingsetstore.go @@ -22,8 +22,11 @@ import ( type ( workingSetStore interface { db.KVStore + PutObject(ns string, key []byte, object any) (err error) + GetObject(ns string, key []byte, object any) error + DeleteObject(ns string, key []byte, object any) error + States(ns string, keys [][]byte, object any) ([][]byte, [][]byte, error) Commit(context.Context, uint64) error - States(string, [][]byte) ([][]byte, [][]byte, error) Digest() hash.Hash256 Finalize(context.Context) error FinalizeTx(context.Context) error @@ -31,6 +34,7 @@ type ( RevertSnapshot(int) error ResetSnapshots() Close() + CreateGenesisStates(context.Context) error } stateDBWorkingSetStore struct { @@ -65,7 +69,23 @@ func (store *stateDBWorkingSetStore) WriteBatch(bat batch.KVStoreBatch) error { return store.flusher.Flush() } +func (store *stateDBWorkingSetStore) PutObject(ns string, key []byte, obj any) error { + store.lock.Lock() + defer store.lock.Unlock() + value, err := state.Serialize(obj) + if err != nil { + return errors.Wrapf(err, "failed to serialize object of ns = %x and key = %x", ns, key) + } + return store.putKV(ns, key, value) +} + func (store *stateDBWorkingSetStore) Put(ns string, key []byte, value []byte) error { + store.lock.Lock() + defer store.lock.Unlock() + return store.putKV(ns, key, value) +} + +func (store *stateDBWorkingSetStore) putKV(ns string, key []byte, value []byte) error { store.lock.Lock() defer store.lock.Unlock() if err := store.flusher.KVStoreWithBuffer().Put(ns, key, value); err != nil { @@ -77,6 +97,10 @@ func (store *stateDBWorkingSetStore) Put(ns string, key []byte, value []byte) er return store.flusher.Flush() } +func (store *stateDBWorkingSetStore) DeleteObject(ns string, key []byte, obj any) error { + return store.Delete(ns, key) +} + func (store *stateDBWorkingSetStore) Delete(ns string, key []byte) error { store.lock.Lock() defer store.lock.Unlock() @@ -127,7 +151,19 @@ func (store *stateDBWorkingSetStore) Stop(context.Context) error { return nil } +func (store *stateDBWorkingSetStore) GetObject(ns string, key []byte, obj any) error { + v, err := store.getKV(ns, key) + if err != nil { + return err + } + return state.Deserialize(obj, v) +} + func (store *stateDBWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { + return store.getKV(ns, key) +} + +func (store *stateDBWorkingSetStore) getKV(ns string, key []byte) ([]byte, error) { data, err := store.flusher.KVStoreWithBuffer().Get(ns, key) if err != nil { if errors.Cause(err) == db.ErrNotExist { @@ -138,7 +174,7 @@ func (store *stateDBWorkingSetStore) Get(ns string, key []byte) ([]byte, error) return data, nil } -func (store *stateDBWorkingSetStore) States(ns string, keys [][]byte) ([][]byte, [][]byte, error) { +func (store *stateDBWorkingSetStore) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { if store.readBuffer { // TODO: after the 180 HF, we can revert readBuffer, and always go this case return readStates(store.flusher.KVStoreWithBuffer(), ns, keys) @@ -162,3 +198,7 @@ func (store *stateDBWorkingSetStore) FinalizeTx(_ context.Context) error { } func (store *stateDBWorkingSetStore) Close() {} + +func (store *stateDBWorkingSetStore) CreateGenesisStates(ctx context.Context) error { + return nil +} diff --git a/state/factory/workingsetstore_erigon.go b/state/factory/workingsetstore_erigon.go index 9ff9c86689..733a29220f 100644 --- a/state/factory/workingsetstore_erigon.go +++ b/state/factory/workingsetstore_erigon.go @@ -31,6 +31,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) const ( @@ -50,6 +51,8 @@ type erigonWorkingSetStore struct { db *erigonDB intraBlockState *erigonstate.IntraBlockState tx kv.Tx + sr erigonstate.StateReader + ctx context.Context } func newErigonDB(path string) *erigonDB { @@ -88,6 +91,8 @@ func (db *erigonDB) newErigonStore(ctx context.Context, height uint64) (*erigonW db: db, tx: tx, intraBlockState: intraBlockState, + sr: r, + ctx: ctx, }, nil } @@ -102,6 +107,8 @@ func (db *erigonDB) newErigonStoreDryrun(ctx context.Context, height uint64) (*e db: db, tx: tx, intraBlockState: intraBlockState, + sr: tsw, + ctx: ctx, }, nil } @@ -274,6 +281,19 @@ func (store *erigonWorkingSetStore) RevertSnapshot(sn int) error { func (store *erigonWorkingSetStore) ResetSnapshots() {} +func (store *erigonWorkingSetStore) PutObject(ns string, key []byte, obj any) (err error) { + storage := store.objectContractStorage(obj) + if storage != nil { + log.L().Debug("put object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj)), zap.Any("content", obj)) + return storage.StoreToContract(ns, key, store.newContractBackend(store.ctx, store.intraBlockState, store.sr)) + } + value, err := state.Serialize(obj) + if err != nil { + return errors.Wrapf(err, "failed to serialize object for namespace %s and key %x", ns, key) + } + return store.Put(ns, key, value) +} + func (store *erigonWorkingSetStore) Put(ns string, key []byte, value []byte) (err error) { // only handling account, contract storage handled by evm adapter // others are ignored @@ -301,6 +321,21 @@ func (store *erigonWorkingSetStore) Put(ns string, key []byte, value []byte) (er return nil } +func (store *erigonWorkingSetStore) GetObject(ns string, key []byte, obj any) error { + storage := store.objectContractStorage(obj) + if storage != nil { + defer func() { + log.L().Debug("get object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) + }() + return storage.LoadFromContract(ns, key, store.newContractBackend(store.ctx, store.intraBlockState, store.sr)) + } + value, err := store.Get(ns, key) + if err != nil { + return errors.Wrapf(err, "failed to get object for namespace %s and key %x", ns, key) + } + return state.Deserialize(obj, value) +} + func (store *erigonWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { switch ns { case AccountKVNamespace: @@ -329,6 +364,15 @@ func (store *erigonWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { } } +func (store *erigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any) error { + storage := store.objectContractStorage(obj) + if storage != nil { + log.L().Debug("delete object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) + return storage.DeleteFromContract(ns, key, store.newContractBackend(store.ctx, store.intraBlockState, store.sr)) + } + return nil +} + func (store *erigonWorkingSetStore) Delete(ns string, key []byte) error { return nil } @@ -341,14 +385,59 @@ func (store *erigonWorkingSetStore) Filter(string, db.Condition, []byte, []byte) return nil, nil, nil } -func (store *erigonWorkingSetStore) States(string, [][]byte) ([][]byte, [][]byte, error) { - return nil, nil, nil +func (store *erigonWorkingSetStore) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { + storage := store.objectContractStorage(obj) + if storage == nil { + return nil, nil, errors.Wrapf(ErrNotSupported, "unsupported object type %T in ns %s", obj, ns) + } + var ( + objs []any + err error + results [][]byte + backend = store.newContractBackend(store.ctx, store.intraBlockState, store.sr) + ) + if len(keys) == 0 { + keys, objs, err = storage.ListFromContract(ns, backend) + } else { + objs, err = storage.BatchFromContract(ns, keys, backend) + } + log.L().Debug("list objs from erigon working set store", + zap.String("ns", ns), + zap.Int("num", len(keys)), + zap.Int("objsize", len(objs)), + zap.String("obj", fmt.Sprintf("%T", obj)), + ) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to list objects from erigon working set store for namespace %s", ns) + } + for i, obj := range objs { + log.L().Debug("list obj from erigon working set store", + zap.String("ns", ns), + log.Hex("key", keys[i]), + zap.String("storage", fmt.Sprintf("%T", obj)), + zap.Any("content", obj), + ) + if obj == nil { + results = append(results, nil) + continue + } + res, err := state.Serialize(obj) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to serialize object for namespace %s and key %x", ns, keys[i]) + } + results = append(results, res) + } + return keys, results, nil } func (store *erigonWorkingSetStore) Digest() hash.Hash256 { return hash.ZeroHash256 } +func (store *erigonWorkingSetStore) CreateGenesisStates(ctx context.Context) error { + return systemcontracts.DeploySystemContractsIfNotExist(store.newContractBackend(ctx, store.intraBlockState, store.sr)) +} + func (store *erigonDB) Height() (uint64, error) { var height uint64 err := store.rw.View(context.Background(), func(tx kv.Tx) error { @@ -369,3 +458,26 @@ func (store *erigonDB) Height() (uint64, error) { } return height, nil } + +func (store *erigonWorkingSetStore) newContractBackend(ctx context.Context, intraBlockState *erigonstate.IntraBlockState, sr erigonstate.StateReader) *contractBacked { + blkCtx := protocol.MustGetBlockCtx(ctx) + g, ok := genesis.ExtractGenesisContext(ctx) + if !ok { + log.S().Panic("failed to extract genesis context from block context") + } + bcCtx := protocol.MustGetBlockchainCtx(ctx) + return NewContractBackend(store.intraBlockState, store.sr, blkCtx.BlockHeight, blkCtx.BlockTimeStamp, &g, bcCtx.EvmNetworkID) +} + +func (store *erigonWorkingSetStore) objectContractStorage(obj any) state.ContractStorage { + if cs, ok := obj.(state.ContractStorage); ok { + return cs + } + if cs, ok := obj.(state.ContractStorageProxy); ok { + return cs.ContractStorageProxy() + } + if cs, ok := obj.(state.ContractStorageStandard); ok { + return state.NewContractStorageStandardWrapper(cs) + } + return nil +} diff --git a/state/factory/workingsetstore_erigon_simulate.go b/state/factory/workingsetstore_erigon_simulate.go index 81f276e437..be38bd6662 100644 --- a/state/factory/workingsetstore_erigon_simulate.go +++ b/state/factory/workingsetstore_erigon_simulate.go @@ -7,6 +7,7 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/state" ) // erigonWorkingSetStoreForSimulate is a working set store that uses erigon as the main store @@ -34,6 +35,21 @@ func (store *erigonWorkingSetStoreForSimulate) Stop(context.Context) error { return nil } +func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, obj any) error { + if _, ok := obj.(state.ContractStorage); ok { + return store.erigonStore.GetObject(ns, key, obj) + } + if _, ok := obj.(*state.Account); !ok && ns == AccountKVNamespace { + return store.store.GetObject(ns, key, obj) + } + switch ns { + case AccountKVNamespace, evm.CodeKVNameSpace: + return store.erigonStore.GetObject(ns, key, obj) + default: + return store.store.GetObject(ns, key, obj) + } +} + func (store *erigonWorkingSetStoreForSimulate) Get(ns string, key []byte) ([]byte, error) { switch ns { case AccountKVNamespace, evm.CodeKVNameSpace: @@ -43,9 +59,12 @@ func (store *erigonWorkingSetStoreForSimulate) Get(ns string, key []byte) ([]byt } } -func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte) ([][]byte, [][]byte, error) { +func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { + if _, ok := obj.(state.ContractStorage); ok { + return store.erigonStore.States(ns, keys, obj) + } // currently only used for staking & poll, no need to read from erigon - return store.store.States(ns, keys) + return store.store.States(ns, keys, obj) } func (store *erigonWorkingSetStoreForSimulate) Finalize(_ context.Context) error { @@ -72,3 +91,7 @@ func (store *erigonWorkingSetStoreForSimulate) Commit(context.Context, uint64) e func (store *erigonWorkingSetStoreForSimulate) Close() { store.erigonStore.Close() } + +func (store *erigonWorkingSetStoreForSimulate) CreateGenesisStates(ctx context.Context) error { + return nil +} diff --git a/state/factory/workingsetstore_test.go b/state/factory/workingsetstore_test.go index dab2f99d5c..347dcbb2e6 100644 --- a/state/factory/workingsetstore_test.go +++ b/state/factory/workingsetstore_test.go @@ -53,7 +53,7 @@ func TestStateDBWorkingSetStore(t *testing.T) { valueInStore, err = store.Get(namespace, key3) require.NoError(err) require.True(bytes.Equal(value3, valueInStore)) - _, valuesInStore, err := store.States(namespace, [][]byte{key1, key2, key3}) + _, valuesInStore, err := store.States(namespace, [][]byte{key1, key2, key3}, nil) require.Equal(3, len(valuesInStore)) require.True(bytes.Equal(value1, valuesInStore[0])) require.True(bytes.Equal(value2, valuesInStore[1])) @@ -66,7 +66,7 @@ func TestStateDBWorkingSetStore(t *testing.T) { require.NoError(store.Delete(namespace, key1)) _, err = store.Get(namespace, key1) require.Error(err) - _, valuesInStore, err = store.States(namespace, [][]byte{key1, key2, key3}) + _, valuesInStore, err = store.States(namespace, [][]byte{key1, key2, key3}, nil) require.Equal(3, len(valuesInStore)) require.Nil(valuesInStore[0]) require.True(bytes.Equal(value2, valuesInStore[1])) diff --git a/state/factory/workingsetstore_with_secondary.go b/state/factory/workingsetstore_with_secondary.go index c4c28130d4..94db5ee389 100644 --- a/state/factory/workingsetstore_with_secondary.go +++ b/state/factory/workingsetstore_with_secondary.go @@ -24,18 +24,22 @@ var ( type reader interface { Get(string, []byte) ([]byte, error) - States(string, [][]byte) ([][]byte, [][]byte, error) + GetObject(string, []byte, any) error + States(string, [][]byte, any) ([][]byte, [][]byte, error) Digest() hash.Hash256 Filter(string, db.Condition, []byte, []byte) ([][]byte, [][]byte, error) } type writer interface { Put(ns string, key []byte, value []byte) error + PutObject(ns string, key []byte, obj any) error Delete(ns string, key []byte) error + DeleteObject(ns string, key []byte, obj any) error Snapshot() int RevertSnapshot(snapshot int) error ResetSnapshots() WriteBatch(batch.KVStoreBatch) error + CreateGenesisStates(context.Context) error } // treat erigon as 3rd output, still read from statedb @@ -92,6 +96,13 @@ func (store *workingSetStoreWithSecondary) Put(ns string, key []byte, value []by return store.writerSecondary.Put(ns, key, value) } +func (store *workingSetStoreWithSecondary) PutObject(ns string, key []byte, obj any) error { + if err := store.writer.PutObject(ns, key, obj); err != nil { + return err + } + return store.writerSecondary.PutObject(ns, key, obj) +} + func (store *workingSetStoreWithSecondary) Delete(ns string, key []byte) error { if err := store.writer.Delete(ns, key); err != nil { return err @@ -99,6 +110,13 @@ func (store *workingSetStoreWithSecondary) Delete(ns string, key []byte) error { return store.writerSecondary.Delete(ns, key) } +func (store *workingSetStoreWithSecondary) DeleteObject(ns string, key []byte, obj any) error { + if err := store.writer.DeleteObject(ns, key, obj); err != nil { + return err + } + return store.writerSecondary.DeleteObject(ns, key, obj) +} + func (store *workingSetStoreWithSecondary) Commit(ctx context.Context, retention uint64) error { // Commit to secondary store first, then commit to main store // This ensures that if the secondary store fails, the main store is not committed @@ -146,3 +164,18 @@ func (store *workingSetStoreWithSecondary) Close() { store.writer.Close() store.writerSecondary.Close() } + +func (store *workingSetStoreWithSecondary) CreateGenesisStates(ctx context.Context) error { + if err := store.writer.CreateGenesisStates(ctx); err != nil { + return err + } + return store.writerSecondary.CreateGenesisStates(ctx) +} + +func (store *workingSetStoreWithSecondary) GetObject(ns string, key []byte, obj any) error { + return store.reader.GetObject(ns, key, obj) +} + +func (store *workingSetStoreWithSecondary) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { + return store.reader.States(ns, keys, obj) +} From c7fa24ac6ba75c35610980d0f1ebc84472bf443f Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 10:33:57 +0800 Subject: [PATCH 06/34] workingsetstore as kvstore --- state/factory/workingset.go | 6 +++- state/factory/workingsetstore.go | 8 ++++- state/factory/workingsetstore_erigon.go | 4 +++ .../workingsetstore_erigon_simulate.go | 17 +++------- state/factory/workingsetstore_test.go | 34 +++++++++++-------- .../factory/workingsetstore_with_secondary.go | 33 ++++-------------- 6 files changed, 45 insertions(+), 57 deletions(-) diff --git a/state/factory/workingset.go b/state/factory/workingset.go index 30ca383d5c..e5ed0c8296 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -1003,7 +1003,11 @@ func (ws *workingSet) NewWorkingSet(ctx context.Context) (*workingSet, error) { if !ws.finalized { return nil, errors.New("workingset has not been finalized yet") } - store, err := ws.workingSetStoreFactory.CreateWorkingSetStore(ctx, ws.height+1, ws.store) + kvStore := ws.store.KVStore() + if kvStore == nil { + return nil, errors.Errorf("KVStore() not supported in %T", ws.store) + } + store, err := ws.workingSetStoreFactory.CreateWorkingSetStore(ctx, ws.height+1, kvStore) if err != nil { return nil, err } diff --git a/state/factory/workingsetstore.go b/state/factory/workingsetstore.go index 57b546ee5c..ce136f9212 100644 --- a/state/factory/workingsetstore.go +++ b/state/factory/workingsetstore.go @@ -21,7 +21,9 @@ import ( type ( workingSetStore interface { - db.KVStore + Start(context.Context) error + Stop(context.Context) error + KVStore() db.KVStore PutObject(ns string, key []byte, object any) (err error) GetObject(ns string, key []byte, object any) error DeleteObject(ns string, key []byte, object any) error @@ -202,3 +204,7 @@ func (store *stateDBWorkingSetStore) Close() {} func (store *stateDBWorkingSetStore) CreateGenesisStates(ctx context.Context) error { return nil } + +func (store *stateDBWorkingSetStore) KVStore() db.KVStore { + return store +} diff --git a/state/factory/workingsetstore_erigon.go b/state/factory/workingsetstore_erigon.go index 733a29220f..2c6b4af7c1 100644 --- a/state/factory/workingsetstore_erigon.go +++ b/state/factory/workingsetstore_erigon.go @@ -481,3 +481,7 @@ func (store *erigonWorkingSetStore) objectContractStorage(obj any) state.Contrac } return nil } + +func (store *erigonWorkingSetStore) KVStore() db.KVStore { + return nil +} diff --git a/state/factory/workingsetstore_erigon_simulate.go b/state/factory/workingsetstore_erigon_simulate.go index be38bd6662..63b43da451 100644 --- a/state/factory/workingsetstore_erigon_simulate.go +++ b/state/factory/workingsetstore_erigon_simulate.go @@ -50,15 +50,6 @@ func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, } } -func (store *erigonWorkingSetStoreForSimulate) Get(ns string, key []byte) ([]byte, error) { - switch ns { - case AccountKVNamespace, evm.CodeKVNameSpace: - return store.erigonStore.Get(ns, key) - default: - return store.store.Get(ns, key) - } -} - func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { if _, ok := obj.(state.ContractStorage); ok { return store.erigonStore.States(ns, keys, obj) @@ -75,10 +66,6 @@ func (store *erigonWorkingSetStoreForSimulate) FinalizeTx(ctx context.Context) e return nil } -func (store *erigonWorkingSetStoreForSimulate) Filter(ns string, cond db.Condition, start, limit []byte) ([][]byte, [][]byte, error) { - return store.store.Filter(ns, cond, start, limit) -} - func (store *erigonWorkingSetStoreForSimulate) Digest() hash.Hash256 { return store.store.Digest() } @@ -95,3 +82,7 @@ func (store *erigonWorkingSetStoreForSimulate) Close() { func (store *erigonWorkingSetStoreForSimulate) CreateGenesisStates(ctx context.Context) error { return nil } + +func (store *erigonWorkingSetStoreForSimulate) KVStore() db.KVStore { + return nil +} diff --git a/state/factory/workingsetstore_test.go b/state/factory/workingsetstore_test.go index 347dcbb2e6..c10ec3de4f 100644 --- a/state/factory/workingsetstore_test.go +++ b/state/factory/workingsetstore_test.go @@ -36,21 +36,23 @@ func TestStateDBWorkingSetStore(t *testing.T) { key3 := []byte("key3") value3 := []byte("value3") t.Run("test kvstore feature", func(t *testing.T) { - _, err := store.Get(namespace, key1) + var value []byte + err := store.GetObject(namespace, key1, &value) require.Error(err) - require.NoError(store.Delete(namespace, key1)) - require.NoError(store.Put(namespace, key1, value1)) - valueInStore, err := store.Get(namespace, key1) + require.NoError(store.DeleteObject(namespace, key1, &value)) + require.NoError(store.PutObject(namespace, key1, value1)) + var valueInStore []byte + err = store.GetObject(namespace, key1, &valueInStore) require.NoError(err) require.True(bytes.Equal(value1, valueInStore)) sn1 := store.Snapshot() - require.NoError(store.Put(namespace, key2, value2)) - valueInStore, err = store.Get(namespace, key2) + require.NoError(store.PutObject(namespace, key2, value2)) + err = store.GetObject(namespace, key2, &valueInStore) require.NoError(err) require.True(bytes.Equal(value2, valueInStore)) store.Snapshot() - require.NoError(store.Put(namespace, key3, value3)) - valueInStore, err = store.Get(namespace, key3) + require.NoError(store.PutObject(namespace, key3, value3)) + err = store.GetObject(namespace, key3, &valueInStore) require.NoError(err) require.True(bytes.Equal(value3, valueInStore)) _, valuesInStore, err := store.States(namespace, [][]byte{key1, key2, key3}, nil) @@ -63,26 +65,27 @@ func TestStateDBWorkingSetStore(t *testing.T) { require.Equal("e1f83be0a44ae601061724990036b8a40edbf81cffc639657c9bb2c5d384defa", hex.EncodeToString(h[:])) }) sn3 := store.Snapshot() - require.NoError(store.Delete(namespace, key1)) - _, err = store.Get(namespace, key1) + require.NoError(store.DeleteObject(namespace, key1, &valueInStore)) + err = store.GetObject(namespace, key1, &valueInStore) require.Error(err) - _, valuesInStore, err = store.States(namespace, [][]byte{key1, key2, key3}, nil) + _, valuesInStore, err = store.States(namespace, [][]byte{key1, key2, key3}, &valueInStore) require.Equal(3, len(valuesInStore)) require.Nil(valuesInStore[0]) require.True(bytes.Equal(value2, valuesInStore[1])) require.True(bytes.Equal(value3, valuesInStore[2])) require.NoError(store.RevertSnapshot(sn3)) - valueInStore, err = store.Get(namespace, key1) + err = store.GetObject(namespace, key1, &valueInStore) require.NoError(err) require.NoError(store.RevertSnapshot(sn1)) require.True(bytes.Equal(value1, valueInStore)) - _, err = store.Get(namespace, key2) + err = store.GetObject(namespace, key2, &valueInStore) require.Error(err) }) t.Run("finalize & commit", func(t *testing.T) { height := uint64(100) ctx := context.Background() - _, err := store.Get(AccountKVNamespace, []byte(CurrentHeightKey)) + var value []byte + err := store.GetObject(AccountKVNamespace, []byte(CurrentHeightKey), &value) require.Error(err) _, err = inMemStore.Get(AccountKVNamespace, []byte(CurrentHeightKey)) require.Error(err) @@ -90,7 +93,8 @@ func TestStateDBWorkingSetStore(t *testing.T) { BlockHeight: height, }) require.NoError(store.Finalize(ctx)) - heightInStore, err := store.Get(AccountKVNamespace, []byte(CurrentHeightKey)) + var heightInStore []byte + err = store.GetObject(AccountKVNamespace, []byte(CurrentHeightKey), &heightInStore) require.NoError(err) require.True(bytes.Equal(heightInStore, byteutil.Uint64ToBytes(height))) _, err = inMemStore.Get(AccountKVNamespace, []byte(CurrentHeightKey)) diff --git a/state/factory/workingsetstore_with_secondary.go b/state/factory/workingsetstore_with_secondary.go index 94db5ee389..d053271d0d 100644 --- a/state/factory/workingsetstore_with_secondary.go +++ b/state/factory/workingsetstore_with_secondary.go @@ -8,7 +8,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/iotexproject/iotex-core/v2/db" - "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/log" ) @@ -23,22 +22,19 @@ var ( ) type reader interface { - Get(string, []byte) ([]byte, error) + // Get(string, []byte) ([]byte, error) GetObject(string, []byte, any) error States(string, [][]byte, any) ([][]byte, [][]byte, error) Digest() hash.Hash256 - Filter(string, db.Condition, []byte, []byte) ([][]byte, [][]byte, error) + // Filter(string, db.Condition, []byte, []byte) ([][]byte, [][]byte, error) } type writer interface { - Put(ns string, key []byte, value []byte) error PutObject(ns string, key []byte, obj any) error - Delete(ns string, key []byte) error DeleteObject(ns string, key []byte, obj any) error Snapshot() int RevertSnapshot(snapshot int) error ResetSnapshots() - WriteBatch(batch.KVStoreBatch) error CreateGenesisStates(context.Context) error } @@ -82,20 +78,6 @@ func (store *workingSetStoreWithSecondary) FinalizeTx(ctx context.Context) error return store.writerSecondary.FinalizeTx(ctx) } -func (store *workingSetStoreWithSecondary) WriteBatch(batch batch.KVStoreBatch) error { - if err := store.writer.WriteBatch(batch); err != nil { - return err - } - return store.writerSecondary.WriteBatch(batch) -} - -func (store *workingSetStoreWithSecondary) Put(ns string, key []byte, value []byte) error { - if err := store.writer.Put(ns, key, value); err != nil { - return err - } - return store.writerSecondary.Put(ns, key, value) -} - func (store *workingSetStoreWithSecondary) PutObject(ns string, key []byte, obj any) error { if err := store.writer.PutObject(ns, key, obj); err != nil { return err @@ -103,13 +85,6 @@ func (store *workingSetStoreWithSecondary) PutObject(ns string, key []byte, obj return store.writerSecondary.PutObject(ns, key, obj) } -func (store *workingSetStoreWithSecondary) Delete(ns string, key []byte) error { - if err := store.writer.Delete(ns, key); err != nil { - return err - } - return store.writerSecondary.Delete(ns, key) -} - func (store *workingSetStoreWithSecondary) DeleteObject(ns string, key []byte, obj any) error { if err := store.writer.DeleteObject(ns, key, obj); err != nil { return err @@ -179,3 +154,7 @@ func (store *workingSetStoreWithSecondary) GetObject(ns string, key []byte, obj func (store *workingSetStoreWithSecondary) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { return store.reader.States(ns, keys, obj) } + +func (store *workingSetStoreWithSecondary) KVStore() db.KVStore { + return nil +} From df5d789384c69840fb8702f4272daee33e229192 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 17:48:15 +0800 Subject: [PATCH 07/34] store rewarding --- action/protocol/rewarding/admin.go | 55 +++++++++++++++++++++++++++ action/protocol/rewarding/fund.go | 27 +++++++++++++ action/protocol/rewarding/protocol.go | 23 ++--------- action/protocol/rewarding/reward.go | 37 +++++++++++++++++- 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/action/protocol/rewarding/admin.go b/action/protocol/rewarding/admin.go index 47b2302030..bd19f66d0e 100644 --- a/action/protocol/rewarding/admin.go +++ b/action/protocol/rewarding/admin.go @@ -6,16 +6,21 @@ package rewarding import ( + "bytes" "context" "math/big" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/rewarding/rewardingpb" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // admin stores the admin data of the rewarding protocol @@ -29,6 +34,8 @@ type admin struct { productivityThreshold uint64 } +var _ state.ContractStorageStandard = (*admin)(nil) + // Serialize serializes admin state into bytes func (a admin) Serialize() ([]byte, error) { gen := rewardingpb.Admin{ @@ -75,11 +82,36 @@ func (a *admin) grantFoundationBonus(epoch uint64) bool { return epoch <= a.foundationBonusLastEpoch } +func (a *admin) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + prefix := hash.Hash160b([]byte(_protocolID)) + if ns == state.AccountKVNamespace { + expectKey := hash.Hash160b(append(prefix[:], _adminKey...)) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV1Index].Address, nil + } else if ns == _v2RewardingNamespace { + expectKey := append(prefix[:], _adminKey...) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV2Index].Address, nil + } else { + return nil, errors.Errorf("unexpected namespace %s", ns) + } +} + +func (a *admin) New() state.ContractStorageStandard { + return &admin{} +} + // exempt stores the addresses that exempt from epoch reward type exempt struct { addrs []address.Address } +var _ state.ContractStorageStandard = (*exempt)(nil) + // Serialize serializes exempt state into bytes func (e *exempt) Serialize() ([]byte, error) { epb := rewardingpb.Exempt{} @@ -106,6 +138,29 @@ func (e *exempt) Deserialize(data []byte) error { return nil } +func (e *exempt) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + prefix := hash.Hash160b([]byte(_protocolID)) + if ns == state.AccountKVNamespace { + expectKey := hash.Hash160b(append(prefix[:], _exemptKey...)) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV1Index].Address, nil + } else if ns == _v2RewardingNamespace { + expectKey := append(prefix[:], _exemptKey...) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV2Index].Address, nil + } else { + return nil, errors.Errorf("unexpected namespace %s", ns) + } +} + +func (e *exempt) New() state.ContractStorageStandard { + return &exempt{} +} + // CreateGenesisStates initializes the rewarding protocol by setting the original admin, block and epoch reward func (p *Protocol) CreateGenesisStates( ctx context.Context, diff --git a/action/protocol/rewarding/fund.go b/action/protocol/rewarding/fund.go index dbd6a3048b..a7830b219e 100644 --- a/action/protocol/rewarding/fund.go +++ b/action/protocol/rewarding/fund.go @@ -6,12 +6,14 @@ package rewarding import ( + "bytes" "context" "math/big" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/go-pkgs/hash" "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotextypes" @@ -20,6 +22,7 @@ import ( accountutil "github.com/iotexproject/iotex-core/v2/action/protocol/account/util" "github.com/iotexproject/iotex-core/v2/action/protocol/rewarding/rewardingpb" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // fund stores the balance of the rewarding fund. The difference between total and available balance should be @@ -29,6 +32,8 @@ type fund struct { unclaimedBalance *big.Int } +var _ state.ContractStorageStandard = (*fund)(nil) + // Serialize serializes fund state into bytes func (f fund) Serialize() ([]byte, error) { gen := rewardingpb.Fund{ @@ -57,6 +62,28 @@ func (f *fund) Deserialize(data []byte) error { return nil } +func (f *fund) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + prefix := hash.Hash160b([]byte(_protocolID)) + if ns == state.AccountKVNamespace { + expectKey := hash.Hash160b(append(prefix[:], _fundKey...)) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV1Index].Address, nil + } else if ns == _v2RewardingNamespace { + expectKey := append(prefix[:], _fundKey...) + if !bytes.Equal(expectKey[:], key) { + return nil, errors.Errorf("unexpected key %x, expected %x", key, expectKey) + } + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV2Index].Address, nil + } + return nil, errors.Errorf("unexpected namespace %s", ns) +} + +func (f *fund) New() state.ContractStorageStandard { + return &fund{} +} + // Deposit deposits token into the rewarding fund func (p *Protocol) Deposit( ctx context.Context, diff --git a/action/protocol/rewarding/protocol.go b/action/protocol/rewarding/protocol.go index 8375a45ace..4ed8190582 100644 --- a/action/protocol/rewarding/protocol.go +++ b/action/protocol/rewarding/protocol.go @@ -138,7 +138,7 @@ func (p *Protocol) migrateValue(sm protocol.StateManager, key []byte, value inte if err := p.putStateV2(sm, key, value); err != nil { return err } - return p.deleteStateV1(sm, key) + return p.deleteStateV1(sm, key, value) } func (p *Protocol) setFoundationBonusExtension(ctx context.Context, sm protocol.StateManager) error { @@ -359,26 +359,9 @@ func (p *Protocol) putStateV2(sm protocol.StateManager, key []byte, value interf return err } -func (p *Protocol) deleteState(ctx context.Context, sm protocol.StateManager, key []byte) error { - if useV2Storage(ctx) { - return p.deleteStateV2(sm, key) - } - return p.deleteStateV1(sm, key) -} - -func (p *Protocol) deleteStateV1(sm protocol.StateManager, key []byte) error { +func (p *Protocol) deleteStateV1(sm protocol.StateManager, key []byte, obj any) error { keyHash := hash.Hash160b(append(p.keyPrefix, key...)) - _, err := sm.DelState(protocol.LegacyKeyOption(keyHash)) - if errors.Cause(err) == state.ErrStateNotExist { - // don't care if not exist - return nil - } - return err -} - -func (p *Protocol) deleteStateV2(sm protocol.StateManager, key []byte) error { - k := append(p.keyPrefix, key...) - _, err := sm.DelState(protocol.KeyOption(k), protocol.NamespaceOption(_v2RewardingNamespace)) + _, err := sm.DelState(protocol.LegacyKeyOption(keyHash), protocol.ObjectOption(obj)) if errors.Cause(err) == state.ErrStateNotExist { // don't care if not exist return nil diff --git a/action/protocol/rewarding/reward.go b/action/protocol/rewarding/reward.go index 67f58ee258..c5462801d7 100644 --- a/action/protocol/rewarding/reward.go +++ b/action/protocol/rewarding/reward.go @@ -26,11 +26,14 @@ import ( "github.com/iotexproject/iotex-core/v2/pkg/enc" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // rewardHistory is the dummy struct to record a reward. Only key matters. type rewardHistory struct{} +var _ state.ContractStorageStandard = (*rewardHistory)(nil) + // Serialize serializes reward history state into bytes func (b rewardHistory) Serialize() ([]byte, error) { gen := rewardingpb.RewardHistory{} @@ -40,11 +43,27 @@ func (b rewardHistory) Serialize() ([]byte, error) { // Deserialize deserializes bytes into reward history state func (b *rewardHistory) Deserialize(data []byte) error { return nil } +func (b *rewardHistory) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns == state.AccountKVNamespace { + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV1Index].Address, nil + } else if ns == _v2RewardingNamespace { + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV2Index].Address, nil + } else { + return nil, errors.Errorf("unexpected namespace %s", ns) + } +} + +func (b *rewardHistory) New() state.ContractStorageStandard { + return &rewardHistory{} +} + // rewardAccount stores the unclaimed balance of an account type rewardAccount struct { balance *big.Int } +var _ state.ContractStorageStandard = (*rewardAccount)(nil) + // Serialize serializes account state into bytes func (a rewardAccount) Serialize() ([]byte, error) { gen := rewardingpb.Account{ @@ -67,6 +86,20 @@ func (a *rewardAccount) Deserialize(data []byte) error { return nil } +func (a *rewardAccount) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns == state.AccountKVNamespace { + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV1Index].Address, nil + } else if ns == _v2RewardingNamespace { + return systemcontracts.SystemContracts[systemcontracts.RewardingContractV2Index].Address, nil + } else { + return nil, errors.Errorf("unexpected namespace %s", ns) + } +} + +func (a *rewardAccount) New() state.ContractStorageStandard { + return &rewardAccount{} +} + // GrantBlockReward grants the block reward (token) to the block producer func (p *Protocol) GrantBlockReward( ctx context.Context, @@ -389,7 +422,7 @@ func (p *Protocol) grantToAccount(ctx context.Context, sm protocol.StateManager, // entry exist // check if from legacy, and we have started using v2, delete v1 if fromLegacy && useV2Storage(ctx) { - if err := p.deleteStateV1(sm, accKey); err != nil { + if err := p.deleteStateV1(sm, accKey, &rewardAccount{}); err != nil { return err } } @@ -416,7 +449,7 @@ func (p *Protocol) claimFromAccount(ctx context.Context, sm protocol.StateManage return err } if fromLegacy && useV2Storage(ctx) { - if err := p.deleteStateV1(sm, accKey); err != nil { + if err := p.deleteStateV1(sm, accKey, &rewardAccount{}); err != nil { return err } } From 085526ffe35f6c01260793bc667522f532a3bd1c Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 21 Aug 2025 17:59:41 +0800 Subject: [PATCH 08/34] store poll --- action/protocol/poll/blockmeta.go | 18 ++++++++++++++++++ action/protocol/poll/util.go | 5 +++-- action/protocol/vote/probationlist.go | 20 ++++++++++++++++++++ action/protocol/vote/unproductivedelegate.go | 20 ++++++++++++++++++++ state/candidate.go | 19 +++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/action/protocol/poll/blockmeta.go b/action/protocol/poll/blockmeta.go index 370e1de590..c807ba39c2 100644 --- a/action/protocol/poll/blockmeta.go +++ b/action/protocol/poll/blockmeta.go @@ -12,7 +12,12 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/poll/blockmetapb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // BlockMeta is a struct to store block metadata @@ -22,6 +27,8 @@ type BlockMeta struct { MintTime time.Time } +var _ state.ContractStorageStandard = (*BlockMeta)(nil) + // NewBlockMeta constructs new blockmeta struct with given fieldss func NewBlockMeta(height uint64, producer string, mintTime time.Time) *BlockMeta { return &BlockMeta{ @@ -70,3 +77,14 @@ func (bm *BlockMeta) LoadProto(pb *blockmetapb.BlockMeta) error { bm.MintTime = mintTime.UTC() return nil } + +func (bm *BlockMeta) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != protocol.SystemNamespace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, protocol.SystemNamespace) + } + return systemcontracts.SystemContracts[systemcontracts.PollBlockMetaContractIndex].Address, nil +} + +func (bm *BlockMeta) New() state.ContractStorageStandard { + return &BlockMeta{} +} diff --git a/action/protocol/poll/util.go b/action/protocol/poll/util.go index f66a052b87..9f0e5656f4 100644 --- a/action/protocol/poll/util.go +++ b/action/protocol/poll/util.go @@ -271,7 +271,7 @@ func shiftCandidates(sm protocol.StateManager) (uint64, error) { if stateHeight != putStateHeight { return 0, errors.Wrap(ErrInconsistentHeight, "failed to shift candidates") } - if delStateHeight, err = sm.DelState(protocol.KeyOption(nextKey[:]), protocol.NamespaceOption(protocol.SystemNamespace)); err != nil { + if delStateHeight, err = sm.DelState(protocol.KeyOption(nextKey[:]), protocol.NamespaceOption(protocol.SystemNamespace), protocol.ObjectOption(&state.CandidateList{})); err != nil { return 0, errors.Wrap( err, "failed to delete next candidatelist after shifting", @@ -306,7 +306,7 @@ func shiftProbationList(sm protocol.StateManager) (uint64, error) { if stateHeight != putStateHeight { return 0, errors.Wrap(ErrInconsistentHeight, "failed to shift candidates") } - if delStateHeight, err = sm.DelState(protocol.KeyOption(nextKey[:]), protocol.NamespaceOption(protocol.SystemNamespace)); err != nil { + if delStateHeight, err = sm.DelState(protocol.KeyOption(nextKey[:]), protocol.NamespaceOption(protocol.SystemNamespace), protocol.ObjectOption(&vote.ProbationList{})); err != nil { return 0, errors.Wrap( err, "failed to delete next probationlist after shifting", @@ -341,6 +341,7 @@ func allBlockMetasFromDB(sr protocol.StateReader, blocksInEpoch uint64) ([]*Bloc protocol.KeysOption(func() ([][]byte, error) { return keys, nil }), + protocol.ObjectOption(&BlockMeta{}), ) if err != nil { return nil, err diff --git a/action/protocol/vote/probationlist.go b/action/protocol/vote/probationlist.go index 97bb33d5ff..9dc9fa1963 100644 --- a/action/protocol/vote/probationlist.go +++ b/action/protocol/vote/probationlist.go @@ -11,7 +11,12 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotextypes" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // ProbationList defines a map where key is candidate's name and value is the counter which counts the unproductivity during probation epoch. @@ -20,6 +25,8 @@ type ProbationList struct { IntensityRate uint32 } +var _ state.ContractStorageStandard = (*ProbationList)(nil) + // NewProbationList returns a new probation list func NewProbationList(intensity uint32) *ProbationList { return &ProbationList{ @@ -74,3 +81,16 @@ func (pl *ProbationList) LoadProto(probationListpb *iotextypes.ProbationCandidat return nil } + +// ContractStorageAddress returns the address of the probation list contract +func (pl *ProbationList) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != protocol.SystemNamespace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, protocol.SystemNamespace) + } + return systemcontracts.SystemContracts[systemcontracts.PollProbationListContractIndex].Address, nil +} + +// New creates a new instance of ProbationList +func (pl *ProbationList) New() state.ContractStorageStandard { + return &ProbationList{} +} diff --git a/action/protocol/vote/unproductivedelegate.go b/action/protocol/vote/unproductivedelegate.go index f1f8914d91..1529dc7e25 100644 --- a/action/protocol/vote/unproductivedelegate.go +++ b/action/protocol/vote/unproductivedelegate.go @@ -11,7 +11,12 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol" updpb "github.com/iotexproject/iotex-core/v2/action/protocol/vote/unproductivedelegatepb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // UnproductiveDelegate defines unproductive delegates information within probation period @@ -21,6 +26,8 @@ type UnproductiveDelegate struct { cacheSize uint64 } +var _ state.ContractStorageStandard = (*UnproductiveDelegate)(nil) + // NewUnproductiveDelegate creates new UnproductiveDelegate with probationperiod and cacheSize func NewUnproductiveDelegate(probationPeriod uint64, cacheSize uint64) (*UnproductiveDelegate, error) { if probationPeriod > cacheSize { @@ -124,3 +131,16 @@ func (upd *UnproductiveDelegate) Equal(upd2 *UnproductiveDelegate) bool { func (upd *UnproductiveDelegate) DelegateList() [][]string { return upd.delegatelist } + +// ContractStorageAddress returns the address of the unproductive delegate contract +func (upd *UnproductiveDelegate) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != protocol.SystemNamespace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, protocol.SystemNamespace) + } + return systemcontracts.SystemContracts[systemcontracts.PollUnproductiveDelegateContractIndex].Address, nil +} + +// New creates a new instance of UnproductiveDelegate +func (upd *UnproductiveDelegate) New() state.ContractStorageStandard { + return &UnproductiveDelegate{} +} diff --git a/state/candidate.go b/state/candidate.go index e26ba3b9bd..4830ba0aa7 100644 --- a/state/candidate.go +++ b/state/candidate.go @@ -15,6 +15,8 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/iotex-core/v2/systemcontracts" + "github.com/iotexproject/iotex-address/address" ) @@ -46,6 +48,8 @@ type ( CandidateMap map[hash.Hash160]*Candidate ) +var _ ContractStorageStandard = (*CandidateList)(nil) + // Equal compares two candidate instances func (c *Candidate) Equal(d *Candidate) bool { if c == d { @@ -150,6 +154,21 @@ func (l *CandidateList) LoadProto(candList *iotextypes.CandidateList) error { return nil } +// ContractStorageAddress returns the address of the candidate list contract +func (l *CandidateList) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns == SystemNamespace { + return systemcontracts.SystemContracts[systemcontracts.PollCandidateListContractIndex].Address, nil + } else if ns == AccountKVNamespace { + return systemcontracts.SystemContracts[systemcontracts.PollLegacyCandidateListContractIndex].Address, nil + } + return nil, errors.Errorf("invalid namespace %s, expected %s or %s", ns, SystemNamespace, AccountKVNamespace) +} + +// New creates a new instance of CandidateList +func (l *CandidateList) New() ContractStorageStandard { + return &CandidateList{} +} + // candidateToPb converts a candidate to protobuf's candidate message func candidateToPb(cand *Candidate) *iotextypes.Candidate { candidatePb := &iotextypes.Candidate{ From 3469fc05adc9f78fd0569f874de53bb8c72c596d Mon Sep 17 00:00:00 2001 From: envestcc Date: Fri, 22 Aug 2025 11:04:11 +0800 Subject: [PATCH 09/34] store staking --- action/protocol/staking/bucket_index.go | 17 ++ action/protocol/staking/bucket_pool.go | 22 +++ action/protocol/staking/candidate.go | 146 ++++++++++++++++++ .../staking/candidate_statemanager.go | 10 +- .../protocol/staking/candidate_statereader.go | 3 +- action/protocol/staking/endorsement.go | 20 +++ .../staking/endorsement_statemanager.go | 2 +- action/protocol/staking/vote_bucket.go | 35 +++++ 8 files changed, 250 insertions(+), 5 deletions(-) diff --git a/action/protocol/staking/bucket_index.go b/action/protocol/staking/bucket_index.go index 698abd6e9e..ea3b703994 100644 --- a/action/protocol/staking/bucket_index.go +++ b/action/protocol/staking/bucket_index.go @@ -12,6 +12,8 @@ import ( "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) type ( @@ -19,6 +21,8 @@ type ( BucketIndices []uint64 ) +var _ state.ContractStorageStandard = (*BucketIndices)(nil) + // Proto converts bucket indices to protobuf func (bis *BucketIndices) Proto() *stakingpb.BucketIndices { bucketIndicesPb := make([]uint64, 0, len(*bis)) @@ -51,6 +55,19 @@ func (bis *BucketIndices) Serialize() ([]byte, error) { return proto.Marshal(bis.Proto()) } +// ContractStorageAddress returns the address of the bucket indices contract +func (bis *BucketIndices) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != _stakingNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _stakingNameSpace) + } + return systemcontracts.SystemContracts[systemcontracts.BucketIndicesContractIndex].Address, nil +} + +// New creates a new instance of BucketIndices +func (bis *BucketIndices) New() state.ContractStorageStandard { + return &BucketIndices{} +} + func (bis *BucketIndices) addBucketIndex(index uint64) { *bis = append(*bis, index) } diff --git a/action/protocol/staking/bucket_pool.go b/action/protocol/staking/bucket_pool.go index 9f970437a7..5deef073ad 100644 --- a/action/protocol/staking/bucket_pool.go +++ b/action/protocol/staking/bucket_pool.go @@ -6,14 +6,18 @@ package staking import ( + "bytes" "math/big" "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // const @@ -49,6 +53,8 @@ type ( } ) +var _ state.ContractStorageStandard = (*totalAmount)(nil) + func (t *totalAmount) Serialize() ([]byte, error) { gen := stakingpb.TotalAmount{ Amount: t.amount.String(), @@ -91,6 +97,22 @@ func (t *totalAmount) SubBalance(amount *big.Int) error { return nil } +// ContractStorageAddress returns the address of the bucket pool contract +func (t *totalAmount) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != _stakingNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _stakingNameSpace) + } + if !bytes.Equal(key, _bucketPoolAddrKey) { + return nil, errors.Errorf("invalid key %x, expected %x", key, _bucketPoolAddrKey) + } + return systemcontracts.SystemContracts[systemcontracts.BucketPoolContractIndex].Address, nil +} + +// New creates a new instance of totalAmount +func (t *totalAmount) New() state.ContractStorageStandard { + return &totalAmount{} +} + // IsDirty returns true if the bucket pool is dirty func (bp *BucketPool) IsDirty() bool { return bp.dirty diff --git a/action/protocol/staking/candidate.go b/action/protocol/staking/candidate.go index 413821e49c..c0f8bd58df 100644 --- a/action/protocol/staking/candidate.go +++ b/action/protocol/staking/candidate.go @@ -10,6 +10,7 @@ import ( "sort" "strings" + "github.com/ethereum/go-ethereum/common" "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" @@ -17,7 +18,9 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) type ( @@ -44,6 +47,9 @@ type ( } ) +var _ state.ContractStorage = (*Candidate)(nil) +var _ state.ContractStorageStandard = (*CandidateList)(nil) + // Clone returns a copy func (d *Candidate) Clone() *Candidate { return &Candidate{ @@ -194,6 +200,133 @@ func (d *Candidate) GetIdentifier() address.Address { return d.Identifier } +func (d *Candidate) storageContractAddress(ns string) (address.Address, error) { + if ns != _candidateNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _candidateNameSpace) + } + // Use the system contract address for candidates + return systemcontracts.SystemContracts[systemcontracts.CandidatesContractIndex].Address, nil +} + +func (d *Candidate) storageContract(ns string, key []byte, backend systemcontracts.ContractBackend) (*systemcontracts.GenericStorageContract, error) { + addr, err := d.storageContractAddress(ns) + if err != nil { + return nil, err + } + contract, err := systemcontracts.NewGenericStorageContract(common.BytesToAddress(addr.Bytes()), backend) + if err != nil { + return nil, errors.Wrapf(err, "failed to create candidate storage contract") + } + return contract, nil +} + +// StoreToContract stores candidate to contract +func (d *Candidate) StoreToContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := d.storageContract(ns, key, backend) + if err != nil { + return err + } + log.S().Debugf("Storing candidate %s to contract %s: %+v", d.GetIdentifier().String(), contract.Address().Hex(), d) + var ( + primaryData []byte + secondaryData []byte + ) + if d.Votes.Sign() > 0 { + secondaryData, err = proto.Marshal(&stakingpb.Candidate{Votes: d.Votes.String()}) + if err != nil { + return errors.Wrap(err, "failed to marshal candidate votes") + } + } + clone := d.Clone() + clone.Votes = big.NewInt(0) + primaryData, err = clone.Serialize() + if err != nil { + return errors.Wrap(err, "failed to serialize candidate") + } + return contract.Put(key, systemcontracts.GenericValue{PrimaryData: primaryData, SecondaryData: secondaryData}) +} + +// LoadFromContract loads candidate from contract +func (d *Candidate) LoadFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + contract, err := d.storageContract(ns, key, backend) + if err != nil { + return err + } + value, err := contract.Get(key) + if err != nil { + return errors.Wrapf(err, "failed to get candidate from contract") + } + if !value.KeyExists { + return errors.Wrapf(state.ErrStateNotExist, "candidate does not exist in contract") + } + if err := d.Deserialize(value.Value.PrimaryData); err != nil { + return errors.Wrap(err, "failed to deserialize candidate") + } + if len(value.Value.SecondaryData) > 0 { + votes := &stakingpb.Candidate{} + if err := proto.Unmarshal(value.Value.SecondaryData, votes); err != nil { + return errors.Wrap(err, "failed to unmarshal candidate votes") + } + var ok bool + d.Votes, ok = new(big.Int).SetString(votes.Votes, 10) + if !ok { + return errors.Wrapf(action.ErrInvalidAmount, "failed to parse candidate votes: %s", votes.Votes) + } + } + log.S().Debugf("Loaded candidate %s from contract %s: %+v", d.GetIdentifier().String(), contract.Address().Hex(), d) + return nil +} + +// DeleteFromContract deletes candidate from contract +func (d *Candidate) DeleteFromContract(ns string, key []byte, backend systemcontracts.ContractBackend) error { + return errors.New("not implemented") +} + +// ListFromContract lists candidates from contract +func (d *Candidate) ListFromContract(ns string, backend systemcontracts.ContractBackend) ([][]byte, []any, error) { + contract, err := d.storageContract(ns, nil, backend) + if err != nil { + return nil, nil, err + } + count, err := contract.Count() + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to count candidates in contract") + } + value, err := contract.List(0, count.Uint64()) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to list candidates in contract") + } + var ( + result = make([]any, 0, len(value.Values)) + ) + log.S().Debugf("Loaded %d candidates from contract %s", len(value.Values), contract.Address().Hex()) + for _, v := range value.Values { + c := &Candidate{} + if err := c.Deserialize(v.PrimaryData); err != nil { + return nil, nil, errors.Wrap(err, "failed to deserialize candidate") + } + if len(v.SecondaryData) > 0 { + votes := &stakingpb.Candidate{} + if err := proto.Unmarshal(v.SecondaryData, votes); err != nil { + return nil, nil, errors.Wrap(err, "failed to unmarshal candidate votes") + } + var ok bool + c.Votes, ok = new(big.Int).SetString(votes.Votes, 10) + if !ok { + return nil, nil, errors.Wrapf(action.ErrInvalidAmount, "failed to parse candidate votes: %s", votes.Votes) + } + } + result = append(result, c) + log.S().Debugf("Loaded candidate %s from contract %s: %+v", c.GetIdentifier().String(), contract.Address().Hex(), c) + } + return value.KeyList, result, nil +} + +// BatchFromContract is not implemented for Candidate +func (d *Candidate) BatchFromContract(ns string, keys [][]byte, backend systemcontracts.ContractBackend) ([]any, error) { + return nil, errors.New("not implemented") +} + func (d *Candidate) toProto() (*stakingpb.Candidate, error) { if d.Owner == nil || d.Operator == nil || d.Reward == nil || len(d.Name) == 0 || d.Votes == nil || d.SelfStake == nil { @@ -360,6 +493,19 @@ func (l *CandidateList) Deserialize(buf []byte) error { return nil } +// ContractStorageAddress returns the address of the candidate list contract +func (l *CandidateList) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != CandsMapNS { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, CandsMapNS) + } + return systemcontracts.SystemContracts[systemcontracts.CandidateMapContractIndex].Address, nil +} + +// New creates a new instance of CandidateList +func (l *CandidateList) New() state.ContractStorageStandard { + return &CandidateList{} +} + func (l CandidateList) toStateCandidateList() (state.CandidateList, error) { list := make(state.CandidateList, 0, len(l)) for _, c := range l { diff --git a/action/protocol/staking/candidate_statemanager.go b/action/protocol/staking/candidate_statemanager.go index b51e248796..5c99c119b4 100644 --- a/action/protocol/staking/candidate_statemanager.go +++ b/action/protocol/staking/candidate_statemanager.go @@ -220,7 +220,9 @@ func (csm *candSM) putBucket(bucket *VoteBucket) (uint64, error) { func (csm *candSM) delBucket(index uint64) error { _, err := csm.DelState( protocol.NamespaceOption(_stakingNameSpace), - protocol.KeyOption(bucketKey(index))) + protocol.KeyOption(bucketKey(index)), + protocol.ObjectOption(&VoteBucket{}), + ) return err } @@ -295,7 +297,9 @@ func (csm *candSM) delBucketIndex(addr address.Address, prefix byte, index uint6 if len(bis) == 0 { _, err = csm.DelState( protocol.NamespaceOption(_stakingNameSpace), - protocol.KeyOption(key)) + protocol.KeyOption(key), + protocol.ObjectOption(&BucketIndices{}), + ) } else { _, err = csm.PutState( &bis, @@ -319,7 +323,7 @@ func (csm *candSM) putCandBucketIndex(addr address.Address, index uint64) error } func (csm *candSM) delCandidate(name address.Address) error { - _, err := csm.DelState(protocol.NamespaceOption(_candidateNameSpace), protocol.KeyOption(name.Bytes())) + _, err := csm.DelState(protocol.NamespaceOption(_candidateNameSpace), protocol.KeyOption(name.Bytes()), protocol.ObjectOption(&Candidate{})) return err } diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index d41e7d6613..15d95b1528 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -229,6 +229,7 @@ func (c *candSR) getAllBuckets() ([]*VoteBucket, uint64, error) { } return keys, nil }), + protocol.ObjectOption(&VoteBucket{}), ) if err != nil { return nil, height, err @@ -310,7 +311,7 @@ func (c *candSR) getCandidate(name address.Address) (*Candidate, uint64, error) } func (c *candSR) getAllCandidates() (CandidateList, uint64, error) { - height, iter, err := c.States(protocol.NamespaceOption(_candidateNameSpace)) + height, iter, err := c.States(protocol.NamespaceOption(_candidateNameSpace), protocol.ObjectOption(&Candidate{})) if err != nil { return nil, height, err } diff --git a/action/protocol/staking/endorsement.go b/action/protocol/staking/endorsement.go index 9ce2ba2a80..08f43b272e 100644 --- a/action/protocol/staking/endorsement.go +++ b/action/protocol/staking/endorsement.go @@ -6,7 +6,11 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/proto" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // EndorsementStatus @@ -34,6 +38,8 @@ type ( } ) +var _ state.ContractStorageStandard = (*Endorsement)(nil) + // String returns a human-readable string of the endorsement status func (s EndorsementStatus) String() string { switch s { @@ -84,6 +90,20 @@ func (e *Endorsement) Deserialize(buf []byte) error { return e.fromProto(pb) } +// ContractStorageAddress returns the address of the endorsement contract +func (e *Endorsement) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != _stakingNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _stakingNameSpace) + } + // Use the system contract address for endorsements + return systemcontracts.SystemContracts[systemcontracts.EndorsementContractIndex].Address, nil +} + +// New creates a new instance of Endorsement +func (e *Endorsement) New() state.ContractStorageStandard { + return &Endorsement{} +} + func (e *Endorsement) toProto() (*stakingpb.Endorsement, error) { return &stakingpb.Endorsement{ ExpireHeight: e.ExpireHeight, diff --git a/action/protocol/staking/endorsement_statemanager.go b/action/protocol/staking/endorsement_statemanager.go index b002512c8a..c273f8d3c1 100644 --- a/action/protocol/staking/endorsement_statemanager.go +++ b/action/protocol/staking/endorsement_statemanager.go @@ -36,7 +36,7 @@ func (esm *EndorsementStateManager) Put(bucketIndex uint64, endorse *Endorsement // Delete deletes the endorsement of a bucket func (esm *EndorsementStateManager) Delete(bucketIndex uint64) error { - _, err := esm.DelState(protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(endorsementKey(bucketIndex))) + _, err := esm.DelState(protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(endorsementKey(bucketIndex)), protocol.ObjectOption(&Endorsement{})) return err } diff --git a/action/protocol/staking/vote_bucket.go b/action/protocol/staking/vote_bucket.go index 6cdc4f9a99..9e11e78a83 100644 --- a/action/protocol/staking/vote_bucket.go +++ b/action/protocol/staking/vote_bucket.go @@ -6,6 +6,7 @@ package staking import ( + "bytes" "math" "math/big" "time" @@ -20,6 +21,8 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) const ( @@ -53,6 +56,9 @@ type ( } ) +var _ state.ContractStorageStandard = (*VoteBucket)(nil) +var _ state.ContractStorageStandard = (*totalBucketCount)(nil) + // NewVoteBucket creates a new vote bucket func NewVoteBucket(cand, owner address.Address, amount *big.Int, duration uint32, ctime time.Time, autoStake bool) *VoteBucket { return &VoteBucket{ @@ -77,6 +83,19 @@ func (vb *VoteBucket) Deserialize(buf []byte) error { return vb.fromProto(pb) } +// ContractStorageAddress returns the address of the bucket contract +func (vb *VoteBucket) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != _stakingNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _stakingNameSpace) + } + return systemcontracts.SystemContracts[systemcontracts.StakingBucketsContractIndex].Address, nil +} + +// New creates a new instance of VoteBucket +func (vb *VoteBucket) New() state.ContractStorageStandard { + return &VoteBucket{} +} + func (vb *VoteBucket) fromProto(pb *stakingpb.Bucket) error { vote, ok := new(big.Int).SetString(pb.GetStakedAmount(), 10) if !ok { @@ -213,6 +232,22 @@ func (tc *totalBucketCount) Count() uint64 { return tc.count } +// ContractStorageAddress returns the address of the total bucket count contract +func (tc *totalBucketCount) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + if ns != _stakingNameSpace { + return nil, errors.Errorf("invalid namespace %s, expected %s", ns, _stakingNameSpace) + } + if !bytes.Equal(key, TotalBucketKey) { + return nil, errors.Errorf("invalid key %x, expected %x", key, TotalBucketKey) + } + return systemcontracts.SystemContracts[systemcontracts.BucketPoolContractIndex].Address, nil +} + +// New creates a new instance of totalBucketCount +func (tc *totalBucketCount) New() state.ContractStorageStandard { + return &totalBucketCount{} +} + func bucketKey(index uint64) []byte { key := []byte{_bucket} return append(key, byteutil.Uint64ToBytesBigEndian(index)...) From 6b7724a86669232a1ce7ebf1a9413315cfed8b90 Mon Sep 17 00:00:00 2001 From: envestcc Date: Fri, 22 Aug 2025 12:09:39 +0800 Subject: [PATCH 10/34] read staking states from erigondb, and deprecat staking index db --- action/protocol/staking/protocol.go | 72 ++----------------- .../protocol/staking/staking_statereader.go | 63 ++-------------- .../staking/staking_statereader_test.go | 4 +- api/coreservice.go | 49 ++++--------- api/web3server.go | 11 ++- blockchain/config.go | 4 +- chainservice/builder.go | 18 +---- chainservice/chainservice.go | 2 - 8 files changed, 43 insertions(+), 180 deletions(-) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 59d510651d..95a1592a7d 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -23,7 +23,6 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" accountutil "github.com/iotexproject/iotex-core/v2/action/protocol/account/util" - "github.com/iotexproject/iotex-core/v2/action/protocol/rolldpos" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" @@ -82,7 +81,6 @@ type ( Protocol struct { addr address.Address config Configuration - candBucketsIndexer *CandidatesBucketsIndexer contractStakingIndexer ContractStakingIndexerWithBucketType contractStakingIndexerV2 ContractStakingIndexer contractStakingIndexerV3 ContractStakingIndexer @@ -151,7 +149,7 @@ func FindProtocol(registry *protocol.Registry) *Protocol { func NewProtocol( helperCtx HelperCtx, cfg *BuilderConfig, - candBucketsIndexer *CandidatesBucketsIndexer, + _ *CandidatesBucketsIndexer, // TODO: remove this parameter contractStakingIndexer ContractStakingIndexerWithBucketType, contractStakingIndexerV2 ContractStakingIndexer, opts ...Option, @@ -205,7 +203,6 @@ func NewProtocol( EndorsementWithdrawWaitingBlocks: cfg.Staking.EndorsementWithdrawWaitingBlocks, MigrateContractAddress: migrateContractAddress, }, - candBucketsIndexer: candBucketsIndexer, voteReviser: voteReviser, patch: NewPatchStore(cfg.StakingPatchDir), contractStakingIndexer: contractStakingIndexer, @@ -431,51 +428,7 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager if err = v.(*ViewData).contractsStake.CreatePreStates(ctx); err != nil { return err } - - if p.candBucketsIndexer == nil { - return nil - } - rp := rolldpos.FindProtocol(protocol.MustGetRegistry(ctx)) - if rp == nil { - return nil - } - currentEpochNum := rp.GetEpochNum(blkCtx.BlockHeight) - if currentEpochNum == 0 { - return nil - } - epochStartHeight := rp.GetEpochHeight(currentEpochNum) - if epochStartHeight != blkCtx.BlockHeight || featureCtx.SkipStakingIndexer { - return nil - } - return p.handleStakingIndexer(ctx, rp.GetEpochHeight(currentEpochNum-1), sm) -} - -func (p *Protocol) handleStakingIndexer(ctx context.Context, epochStartHeight uint64, sm protocol.StateManager) error { - csr, err := ConstructBaseView(sm) - if err != nil { - return err - } - allBuckets, _, err := csr.getAllBuckets() - if err != nil && errors.Cause(err) != state.ErrStateNotExist { - return err - } - buckets, err := toIoTeXTypesVoteBucketList(sm, allBuckets) - if err != nil { - return err - } - err = p.candBucketsIndexer.PutBuckets(epochStartHeight, buckets) - if err != nil { - return err - } - all, _, err := csr.getAllCandidates() - if err != nil && errors.Cause(err) != state.ErrStateNotExist { - return err - } - candidateList, err := toIoTeXTypesCandidateListV2(csr, all, protocol.MustGetFeatureCtx(ctx)) - if err != nil { - return err - } - return p.candBucketsIndexer.PutCandidates(epochStartHeight, candidateList) + return nil } // PreCommit performs pre-commit @@ -746,21 +699,12 @@ func (p *Protocol) ReadState(ctx context.Context, sr protocol.StateReader, metho if p.contractStakingIndexerV3 != nil { indexers = append(indexers, NewDelayTolerantIndexer(p.contractStakingIndexerV3, time.Second)) } - stakeSR, err := newCompositeStakingStateReader(p.candBucketsIndexer, sr, p.calculateVoteWeight, indexers...) + // TODO: make sure view states is at the same height as sr + nativeSR, err := ConstructBaseView(sr) if err != nil { return nil, 0, err } - - // get height arg - inputHeight, err := sr.Height() - if err != nil { - return nil, 0, err - } - epochStartHeight := inputHeight - if rp := rolldpos.FindProtocol(protocol.MustGetRegistry(ctx)); rp != nil { - epochStartHeight = rp.GetEpochHeight(rp.GetEpochNum(inputHeight)) - } - nativeSR, err := ConstructBaseView(sr) + stakeSR, err := newCompositeStakingStateReader(nativeSR, p.calculateVoteWeight, indexers...) if err != nil { return nil, 0, err } @@ -771,11 +715,7 @@ func (p *Protocol) ReadState(ctx context.Context, sr protocol.StateReader, metho ) switch m.GetMethod() { case iotexapi.ReadStakingDataMethod_BUCKETS: - if epochStartHeight != 0 && p.candBucketsIndexer != nil { - resp, height, err = p.candBucketsIndexer.GetBuckets(epochStartHeight, r.GetBuckets().GetPagination().GetOffset(), r.GetBuckets().GetPagination().GetLimit()) - } else { - resp, height, err = nativeSR.readStateBuckets(ctx, r.GetBuckets()) - } + resp, height, err = nativeSR.readStateBuckets(ctx, r.GetBuckets()) case iotexapi.ReadStakingDataMethod_BUCKETS_BY_VOTER: resp, height, err = nativeSR.readStateBucketsByVoter(ctx, r.GetBucketsByVoter()) case iotexapi.ReadStakingDataMethod_BUCKETS_BY_CANDIDATE: diff --git a/action/protocol/staking/staking_statereader.go b/action/protocol/staking/staking_statereader.go index b7eb3293b0..bd24c0c272 100644 --- a/action/protocol/staking/staking_statereader.go +++ b/action/protocol/staking/staking_statereader.go @@ -15,61 +15,31 @@ import ( "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/action/protocol/rolldpos" ) type ( // compositeStakingStateReader is the compositive staking state reader, which combine native and contract staking compositeStakingStateReader struct { contractIndexers []ContractStakingIndexer - nativeIndexer *CandidatesBucketsIndexer nativeSR CandidateStateReader calculateVoteWeight func(v *VoteBucket, selfStake bool) *big.Int } ) // newCompositeStakingStateReader creates a new compositive staking state reader -func newCompositeStakingStateReader(nativeIndexer *CandidatesBucketsIndexer, sr protocol.StateReader, calculateVoteWeight func(v *VoteBucket, selfStake bool) *big.Int, contractIndexers ...ContractStakingIndexer) (*compositeStakingStateReader, error) { - nativeSR, err := ConstructBaseView(sr) - if err != nil { - return nil, err - } +func newCompositeStakingStateReader(csr CandidateStateReader, calculateVoteWeight func(v *VoteBucket, selfStake bool) *big.Int, contractIndexers ...ContractStakingIndexer) (*compositeStakingStateReader, error) { return &compositeStakingStateReader{ contractIndexers: contractIndexers, - nativeIndexer: nativeIndexer, - nativeSR: nativeSR, + nativeSR: csr, calculateVoteWeight: calculateVoteWeight, }, nil } func (c *compositeStakingStateReader) readStateBuckets(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBuckets) (*iotextypes.VoteBucketList, uint64, error) { - // get height arg - inputHeight, err := c.nativeSR.SR().Height() + buckets, height, err := c.nativeSR.readStateBuckets(ctx, req) if err != nil { return nil, 0, err } - epochStartHeight := inputHeight - if rp := rolldpos.FindProtocol(protocol.MustGetRegistry(ctx)); rp != nil { - epochStartHeight = rp.GetEpochHeight(rp.GetEpochNum(inputHeight)) - } - - var ( - buckets *iotextypes.VoteBucketList - height uint64 - ) - if epochStartHeight != 0 && c.nativeIndexer != nil { - // read native buckets from indexer - buckets, height, err = c.nativeIndexer.GetBuckets(epochStartHeight, req.GetPagination().GetOffset(), req.GetPagination().GetLimit()) - if err != nil { - return nil, 0, err - } - } else { - // read native buckets from state - buckets, height, err = c.nativeSR.readStateBuckets(ctx, req) - if err != nil { - return nil, 0, err - } - } if !c.isContractStakingEnabled() { buckets.Buckets = getPageOfArray(buckets.Buckets, int(req.GetPagination().GetOffset()), int(req.GetPagination().GetLimit())) @@ -213,34 +183,11 @@ func (c *compositeStakingStateReader) readStateBucketCount(ctx context.Context, } func (c *compositeStakingStateReader) readStateCandidates(ctx context.Context, req *iotexapi.ReadStakingDataRequest_Candidates) (*iotextypes.CandidateListV2, uint64, error) { - // get height arg - inputHeight, err := c.nativeSR.SR().Height() + // read native candidates + candidates, height, err := c.nativeSR.readStateCandidates(ctx, req) if err != nil { return nil, 0, err } - epochStartHeight := inputHeight - if rp := rolldpos.FindProtocol(protocol.MustGetRegistry(ctx)); rp != nil { - epochStartHeight = rp.GetEpochHeight(rp.GetEpochNum(inputHeight)) - } - - // read native candidates - var ( - candidates *iotextypes.CandidateListV2 - height uint64 - ) - if epochStartHeight != 0 && c.nativeIndexer != nil { - // read candidates from indexer - candidates, height, err = c.nativeIndexer.GetCandidates(epochStartHeight, req.GetPagination().GetOffset(), req.GetPagination().GetLimit()) - if err != nil { - return nil, 0, err - } - } else { - // read candidates from native state - candidates, height, err = c.nativeSR.readStateCandidates(ctx, req) - if err != nil { - return nil, 0, err - } - } if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes { return candidates, height, nil } diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index 307eb9e1df..3589f0524a 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -137,7 +137,9 @@ func TestStakingStateReader(t *testing.T) { } return buckets, nil }).AnyTimes() - stakeSR, err := newCompositeStakingStateReader(nil, sf, func(v *VoteBucket, selfStake bool) *big.Int { + csr, err := ConstructBaseView(sf) + r.NoError(err) + stakeSR, err := newCompositeStakingStateReader(csr, func(v *VoteBucket, selfStake bool) *big.Int { return v.StakedAmount }, contractIndexer) r.NoError(err) diff --git a/api/coreservice.go b/api/coreservice.go index 5eeb6f06e0..ba82512c94 100644 --- a/api/coreservice.go +++ b/api/coreservice.go @@ -1046,13 +1046,17 @@ func (core *coreService) readState(ctx context.Context, p protocol.Protocol, hei return d, h, nil } - // TODO: need to complete the context - ctx, err := core.bc.Context(ctx) - if err != nil { - return nil, 0, err + var err error + inputHeight := tipHeight + if height != "" { + inputHeight, err = strconv.ParseUint(height, 0, 64) + if err != nil { + return nil, 0, err + } } + ctx, err = core.bc.ContextAtHeight(ctx, inputHeight) ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ - BlockHeight: tipHeight, + BlockHeight: inputHeight, }) ctx = genesis.WithGenesisContext( protocol.WithRegistry(ctx, core.registry), @@ -1060,36 +1064,13 @@ func (core *coreService) readState(ctx context.Context, p protocol.Protocol, hei ) ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) - if height != "" { - inputHeight, err := strconv.ParseUint(height, 0, 64) - if err != nil { - return nil, 0, err - } - rp := rolldpos.FindProtocol(core.registry) - if rp != nil { - tipEpochNum := rp.GetEpochNum(tipHeight) - inputEpochNum := rp.GetEpochNum(inputHeight) - if inputEpochNum < tipEpochNum { - inputHeight = rp.GetEpochHeight(inputEpochNum) - } - } - if inputHeight < tipHeight { - // old data, wrap to history state reader - historySR, err := core.sf.WorkingSetAtHeight(ctx, inputHeight) - if err != nil { - return nil, 0, err - } - defer historySR.Close() - d, h, err := p.ReadState(ctx, historySR, methodName, arguments...) - if err == nil { - key.Height = strconv.FormatUint(h, 10) - core.readCache.Put(key.Hash(), d) - } - return d, h, err - } + historySR, err := core.sf.WorkingSetAtHeight(ctx, inputHeight) + if err != nil { + return nil, 0, err } - // TODO: need to distinguish user error and system error - d, h, err := p.ReadState(ctx, core.sf, methodName, arguments...) + defer historySR.Close() + // read state + d, h, err := p.ReadState(ctx, historySR, methodName, arguments...) if err == nil { key.Height = strconv.FormatUint(h, 10) core.readCache.Put(key.Hash(), d) diff --git a/api/web3server.go b/api/web3server.go index 1d69c1f1ef..6f616b795f 100644 --- a/api/web3server.go +++ b/api/web3server.go @@ -467,6 +467,13 @@ func (svr *web3Handler) call(ctx context.Context, in *gjson.Result) (interface{} if err != nil { return nil, err } + height, _, err := svr.blockNumberOrHashToHeight(callMsg.BlockNumberOrHash) + if err != nil { + return nil, err + } + if height == 0 { + height = svr.coreService.TipHeight() + } var ( to = callMsg.To data = callMsg.Data @@ -479,7 +486,7 @@ func (svr *web3Handler) call(ctx context.Context, in *gjson.Result) (interface{} if err != nil { return nil, err } - states, err := svr.coreService.ReadState("staking", "", sctx.Parameters().MethodName, sctx.Parameters().Arguments) + states, err := svr.coreService.ReadState("staking", fmt.Sprintf("%d", height), sctx.Parameters().MethodName, sctx.Parameters().Arguments) if err != nil { return nil, err } @@ -494,7 +501,7 @@ func (svr *web3Handler) call(ctx context.Context, in *gjson.Result) (interface{} if err != nil { return nil, err } - states, err := svr.coreService.ReadState("rewarding", "", sctx.Parameters().MethodName, sctx.Parameters().Arguments) + states, err := svr.coreService.ReadState("rewarding", fmt.Sprintf("%d", height), sctx.Parameters().MethodName, sctx.Parameters().Arguments) if err != nil { return nil, err } diff --git a/blockchain/config.go b/blockchain/config.go index c14396ba8d..7e73b33d9b 100644 --- a/blockchain/config.go +++ b/blockchain/config.go @@ -33,7 +33,8 @@ type ( IndexDBPath string `yaml:"indexDBPath"` BloomfilterIndexDBPath string `yaml:"bloomfilterIndexDBPath"` CandidateIndexDBPath string `yaml:"candidateIndexDBPath"` - StakingIndexDBPath string `yaml:"stakingIndexDBPath"` + // deprecated + StakingIndexDBPath string `yaml:"stakingIndexDBPath"` // deprecated SGDIndexDBPath string `yaml:"sgdIndexDBPath"` ContractStakingIndexDBPath string `yaml:"contractStakingIndexDBPath"` @@ -66,6 +67,7 @@ type ( // EnableStakingProtocol enables staking protocol EnableStakingProtocol bool `yaml:"enableStakingProtocol"` // EnableStakingIndexer enables staking indexer + // deprecated EnableStakingIndexer bool `yaml:"enableStakingIndexer"` // AllowedBlockGasResidue is the amount of gas remained when block producer could stop processing more actions AllowedBlockGasResidue uint64 `yaml:"allowedBlockGasResidue"` diff --git a/chainservice/builder.go b/chainservice/builder.go index a3ec0ff691..812ed9c6b0 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -406,7 +406,7 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } func (builder *Builder) buildGatewayComponents(forTest bool) error { - indexer, bfIndexer, candidateIndexer, candBucketsIndexer, err := builder.createGateWayComponents(forTest) + indexer, bfIndexer, candidateIndexer, err := builder.createGateWayComponents(forTest) if err != nil { return errors.Wrapf(err, "failed to create gateway components") } @@ -414,10 +414,6 @@ func (builder *Builder) buildGatewayComponents(forTest bool) error { if builder.cs.candidateIndexer != nil { builder.cs.lifecycle.Add(builder.cs.candidateIndexer) } - builder.cs.candBucketsIndexer = candBucketsIndexer - if builder.cs.candBucketsIndexer != nil { - builder.cs.lifecycle.Add(builder.cs.candBucketsIndexer) - } builder.cs.bfIndexer = bfIndexer builder.cs.indexer = indexer @@ -428,7 +424,6 @@ func (builder *Builder) createGateWayComponents(forTest bool) ( indexer blockindex.Indexer, bfIndexer blockindex.BloomFilterIndexer, candidateIndexer *poll.CandidateIndexer, - candBucketsIndexer *staking.CandidatesBucketsIndexer, err error, ) { _, gateway := builder.cfg.Plugins[config.GatewayPlugin] @@ -449,9 +444,6 @@ func (builder *Builder) createGateWayComponents(forTest bool) ( if err != nil { return } - if builder.cfg.Chain.EnableStakingIndexer { - candBucketsIndexer, err = staking.NewStakingCandidatesBucketsIndexer(db.NewMemKVStore()) - } return } dbConfig := builder.cfg.DB @@ -474,12 +466,6 @@ func (builder *Builder) createGateWayComponents(forTest bool) ( if err != nil { return } - - // create staking indexer - if builder.cfg.Chain.EnableStakingIndexer { - dbConfig.DbPath = builder.cfg.Chain.StakingIndexDBPath - candBucketsIndexer, err = staking.NewStakingCandidatesBucketsIndexer(db.NewBoltDB(dbConfig)) - } return } @@ -704,7 +690,7 @@ func (builder *Builder) registerStakingProtocol() error { CorrectCandSelfStakeHeight: builder.cfg.Genesis.VanuatuBlockHeight, }, }, - builder.cs.candBucketsIndexer, + nil, builder.cs.contractStakingIndexer, builder.cs.contractStakingIndexerV2, opts..., diff --git a/chainservice/chainservice.go b/chainservice/chainservice.go index 0eee347530..e844db349b 100644 --- a/chainservice/chainservice.go +++ b/chainservice/chainservice.go @@ -24,7 +24,6 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/poll" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking" "github.com/iotexproject/iotex-core/v2/actpool" "github.com/iotexproject/iotex-core/v2/actsync" "github.com/iotexproject/iotex-core/v2/api" @@ -73,7 +72,6 @@ type ChainService struct { indexer blockindex.Indexer bfIndexer blockindex.BloomFilterIndexer candidateIndexer *poll.CandidateIndexer - candBucketsIndexer *staking.CandidatesBucketsIndexer contractStakingIndexer *contractstaking.Indexer contractStakingIndexerV2 stakingindex.StakingIndexer contractStakingIndexerV3 stakingindex.StakingIndexer From 2d00d27eb17f879271603c4475d64c71c141d86f Mon Sep 17 00:00:00 2001 From: envestcc Date: Fri, 29 Aug 2025 08:45:56 +0800 Subject: [PATCH 11/34] secondaryOnly --- action/protocol/managers.go | 17 ++++++++--- state/factory/workingset.go | 8 ++--- state/factory/workingsetstore.go | 28 ++++++++++++----- state/factory/workingsetstore_erigon.go | 8 ++--- .../workingsetstore_erigon_simulate.go | 16 +++++----- .../factory/workingsetstore_with_secondary.go | 30 +++++++++---------- 6 files changed, 63 insertions(+), 44 deletions(-) diff --git a/action/protocol/managers.go b/action/protocol/managers.go index 7b24c616c6..57dcbfbd85 100644 --- a/action/protocol/managers.go +++ b/action/protocol/managers.go @@ -62,13 +62,22 @@ func ObjectOption(obj any) StateOption { } } +// SecondaryOnlyOption sets the option to only access secondary workingset store +func SecondaryOnlyOption() StateOption { + return func(cfg *StateConfig) error { + cfg.SecondaryOnly = true + return nil + } +} + type ( // StateConfig is the config for accessing stateDB StateConfig struct { - Namespace string // namespace used by state's storage - Key []byte - Keys [][]byte - Object any // object used by state's storage + Namespace string // namespace used by state's storage + Key []byte + Keys [][]byte + Object any // object used by state's storage + SecondaryOnly bool // only access secondary workingset store } // StateOption sets parameter for access state diff --git a/state/factory/workingset.go b/state/factory/workingset.go index e5ed0c8296..ddfb1949de 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -336,7 +336,7 @@ func (ws *workingSet) State(s interface{}, opts ...protocol.StateOption) (uint64 if cfg.Keys != nil { return 0, errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet") } - return ws.height, ws.store.GetObject(cfg.Namespace, cfg.Key, s) + return ws.height, ws.store.GetObject(cfg.Namespace, cfg.Key, s, cfg.SecondaryOnly) } func (ws *workingSet) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) { @@ -347,7 +347,7 @@ func (ws *workingSet) States(opts ...protocol.StateOption) (uint64, state.Iterat if cfg.Key != nil { return 0, nil, errors.Wrap(ErrNotSupported, "Read states with key option has not been implemented yet") } - keys, values, err := ws.store.States(cfg.Namespace, cfg.Keys, cfg.Object) + keys, values, err := ws.store.States(cfg.Namespace, cfg.Keys, cfg.Object, cfg.SecondaryOnly) if err != nil { return 0, nil, err } @@ -365,7 +365,7 @@ func (ws *workingSet) PutState(s interface{}, opts ...protocol.StateOption) (uin if err != nil { return ws.height, err } - return ws.height, ws.store.PutObject(cfg.Namespace, cfg.Key, s) + return ws.height, ws.store.PutObject(cfg.Namespace, cfg.Key, s, cfg.SecondaryOnly) } // DelState deletes a state from DB @@ -375,7 +375,7 @@ func (ws *workingSet) DelState(opts ...protocol.StateOption) (uint64, error) { if err != nil { return ws.height, err } - return ws.height, ws.store.DeleteObject(cfg.Namespace, cfg.Key, cfg.Object) + return ws.height, ws.store.DeleteObject(cfg.Namespace, cfg.Key, cfg.Object, cfg.SecondaryOnly) } // ReadView reads the view diff --git a/state/factory/workingsetstore.go b/state/factory/workingsetstore.go index ce136f9212..6e9b4ec6c7 100644 --- a/state/factory/workingsetstore.go +++ b/state/factory/workingsetstore.go @@ -24,10 +24,10 @@ type ( Start(context.Context) error Stop(context.Context) error KVStore() db.KVStore - PutObject(ns string, key []byte, object any) (err error) - GetObject(ns string, key []byte, object any) error - DeleteObject(ns string, key []byte, object any) error - States(ns string, keys [][]byte, object any) ([][]byte, [][]byte, error) + PutObject(ns string, key []byte, object any, secondaryOnly bool) (err error) + GetObject(ns string, key []byte, object any, secondaryOnly bool) error + DeleteObject(ns string, key []byte, object any, secondaryOnly bool) error + States(ns string, keys [][]byte, object any, secondaryOnly bool) ([][]byte, [][]byte, error) Commit(context.Context, uint64) error Digest() hash.Hash256 Finalize(context.Context) error @@ -71,7 +71,10 @@ func (store *stateDBWorkingSetStore) WriteBatch(bat batch.KVStoreBatch) error { return store.flusher.Flush() } -func (store *stateDBWorkingSetStore) PutObject(ns string, key []byte, obj any) error { +func (store *stateDBWorkingSetStore) PutObject(ns string, key []byte, obj any, secondaryOnly bool) error { + if secondaryOnly { + return nil + } store.lock.Lock() defer store.lock.Unlock() value, err := state.Serialize(obj) @@ -99,7 +102,10 @@ func (store *stateDBWorkingSetStore) putKV(ns string, key []byte, value []byte) return store.flusher.Flush() } -func (store *stateDBWorkingSetStore) DeleteObject(ns string, key []byte, obj any) error { +func (store *stateDBWorkingSetStore) DeleteObject(ns string, key []byte, obj any, secondaryOnly bool) error { + if secondaryOnly { + return nil + } return store.Delete(ns, key) } @@ -153,7 +159,10 @@ func (store *stateDBWorkingSetStore) Stop(context.Context) error { return nil } -func (store *stateDBWorkingSetStore) GetObject(ns string, key []byte, obj any) error { +func (store *stateDBWorkingSetStore) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { + if secondaryOnly { + return errors.Wrap(state.ErrStateNotExist, "working set store not support secondary only") + } v, err := store.getKV(ns, key) if err != nil { return err @@ -176,7 +185,10 @@ func (store *stateDBWorkingSetStore) getKV(ns string, key []byte) ([]byte, error return data, nil } -func (store *stateDBWorkingSetStore) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { +func (store *stateDBWorkingSetStore) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { + if secondaryOnly { + return nil, nil, errors.Wrap(state.ErrStateNotExist, "working set store not support secondary only") + } if store.readBuffer { // TODO: after the 180 HF, we can revert readBuffer, and always go this case return readStates(store.flusher.KVStoreWithBuffer(), ns, keys) diff --git a/state/factory/workingsetstore_erigon.go b/state/factory/workingsetstore_erigon.go index 2c6b4af7c1..eae419861e 100644 --- a/state/factory/workingsetstore_erigon.go +++ b/state/factory/workingsetstore_erigon.go @@ -281,7 +281,7 @@ func (store *erigonWorkingSetStore) RevertSnapshot(sn int) error { func (store *erigonWorkingSetStore) ResetSnapshots() {} -func (store *erigonWorkingSetStore) PutObject(ns string, key []byte, obj any) (err error) { +func (store *erigonWorkingSetStore) PutObject(ns string, key []byte, obj any, secondaryOnly bool) (err error) { storage := store.objectContractStorage(obj) if storage != nil { log.L().Debug("put object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj)), zap.Any("content", obj)) @@ -321,7 +321,7 @@ func (store *erigonWorkingSetStore) Put(ns string, key []byte, value []byte) (er return nil } -func (store *erigonWorkingSetStore) GetObject(ns string, key []byte, obj any) error { +func (store *erigonWorkingSetStore) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { storage := store.objectContractStorage(obj) if storage != nil { defer func() { @@ -364,7 +364,7 @@ func (store *erigonWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { } } -func (store *erigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any) error { +func (store *erigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any, secondaryOnly bool) error { storage := store.objectContractStorage(obj) if storage != nil { log.L().Debug("delete object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) @@ -385,7 +385,7 @@ func (store *erigonWorkingSetStore) Filter(string, db.Condition, []byte, []byte) return nil, nil, nil } -func (store *erigonWorkingSetStore) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { +func (store *erigonWorkingSetStore) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { storage := store.objectContractStorage(obj) if storage == nil { return nil, nil, errors.Wrapf(ErrNotSupported, "unsupported object type %T in ns %s", obj, ns) diff --git a/state/factory/workingsetstore_erigon_simulate.go b/state/factory/workingsetstore_erigon_simulate.go index 63b43da451..61c35a3c59 100644 --- a/state/factory/workingsetstore_erigon_simulate.go +++ b/state/factory/workingsetstore_erigon_simulate.go @@ -35,27 +35,27 @@ func (store *erigonWorkingSetStoreForSimulate) Stop(context.Context) error { return nil } -func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, obj any) error { +func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { if _, ok := obj.(state.ContractStorage); ok { - return store.erigonStore.GetObject(ns, key, obj) + return store.erigonStore.GetObject(ns, key, obj, secondaryOnly) } if _, ok := obj.(*state.Account); !ok && ns == AccountKVNamespace { - return store.store.GetObject(ns, key, obj) + return store.store.GetObject(ns, key, obj, secondaryOnly) } switch ns { case AccountKVNamespace, evm.CodeKVNameSpace: - return store.erigonStore.GetObject(ns, key, obj) + return store.erigonStore.GetObject(ns, key, obj, secondaryOnly) default: - return store.store.GetObject(ns, key, obj) + return store.store.GetObject(ns, key, obj, secondaryOnly) } } -func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { +func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { if _, ok := obj.(state.ContractStorage); ok { - return store.erigonStore.States(ns, keys, obj) + return store.erigonStore.States(ns, keys, obj, secondaryOnly) } // currently only used for staking & poll, no need to read from erigon - return store.store.States(ns, keys, obj) + return store.store.States(ns, keys, obj, secondaryOnly) } func (store *erigonWorkingSetStoreForSimulate) Finalize(_ context.Context) error { diff --git a/state/factory/workingsetstore_with_secondary.go b/state/factory/workingsetstore_with_secondary.go index d053271d0d..f7f72e8905 100644 --- a/state/factory/workingsetstore_with_secondary.go +++ b/state/factory/workingsetstore_with_secondary.go @@ -22,16 +22,14 @@ var ( ) type reader interface { - // Get(string, []byte) ([]byte, error) - GetObject(string, []byte, any) error - States(string, [][]byte, any) ([][]byte, [][]byte, error) + GetObject(string, []byte, any, bool) error + States(string, [][]byte, any, bool) ([][]byte, [][]byte, error) Digest() hash.Hash256 - // Filter(string, db.Condition, []byte, []byte) ([][]byte, [][]byte, error) } type writer interface { - PutObject(ns string, key []byte, obj any) error - DeleteObject(ns string, key []byte, obj any) error + PutObject(ns string, key []byte, obj any, secondaryOnly bool) error + DeleteObject(ns string, key []byte, obj any, secondaryOnly bool) error Snapshot() int RevertSnapshot(snapshot int) error ResetSnapshots() @@ -78,18 +76,18 @@ func (store *workingSetStoreWithSecondary) FinalizeTx(ctx context.Context) error return store.writerSecondary.FinalizeTx(ctx) } -func (store *workingSetStoreWithSecondary) PutObject(ns string, key []byte, obj any) error { - if err := store.writer.PutObject(ns, key, obj); err != nil { +func (store *workingSetStoreWithSecondary) PutObject(ns string, key []byte, obj any, secondaryOnly bool) error { + if err := store.writer.PutObject(ns, key, obj, secondaryOnly); err != nil { return err } - return store.writerSecondary.PutObject(ns, key, obj) + return store.writerSecondary.PutObject(ns, key, obj, secondaryOnly) } -func (store *workingSetStoreWithSecondary) DeleteObject(ns string, key []byte, obj any) error { - if err := store.writer.DeleteObject(ns, key, obj); err != nil { +func (store *workingSetStoreWithSecondary) DeleteObject(ns string, key []byte, obj any, secondaryOnly bool) error { + if err := store.writer.DeleteObject(ns, key, obj, secondaryOnly); err != nil { return err } - return store.writerSecondary.DeleteObject(ns, key, obj) + return store.writerSecondary.DeleteObject(ns, key, obj, secondaryOnly) } func (store *workingSetStoreWithSecondary) Commit(ctx context.Context, retention uint64) error { @@ -147,12 +145,12 @@ func (store *workingSetStoreWithSecondary) CreateGenesisStates(ctx context.Conte return store.writerSecondary.CreateGenesisStates(ctx) } -func (store *workingSetStoreWithSecondary) GetObject(ns string, key []byte, obj any) error { - return store.reader.GetObject(ns, key, obj) +func (store *workingSetStoreWithSecondary) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { + return store.reader.GetObject(ns, key, obj, secondaryOnly) } -func (store *workingSetStoreWithSecondary) States(ns string, keys [][]byte, obj any) ([][]byte, [][]byte, error) { - return store.reader.States(ns, keys, obj) +func (store *workingSetStoreWithSecondary) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { + return store.reader.States(ns, keys, obj, secondaryOnly) } func (store *workingSetStoreWithSecondary) KVStore() db.KVStore { From ad4c6b0ddecbcb1ae548656cd7926d3aa4c9c132 Mon Sep 17 00:00:00 2001 From: CoderZhi Date: Tue, 2 Sep 2025 18:43:58 +0300 Subject: [PATCH 12/34] Store staking in trie (#4680) * store contract staking data in trie * fix unit test and address comment --- action/protocol/context.go | 2 + action/protocol/mock_protocol.go | 2 +- action/protocol/protocol.go | 6 +- action/protocol/staking/bucket_index_test.go | 12 +- action/protocol/staking/bucket_pool.go | 2 +- action/protocol/staking/candidate_center.go | 8 +- .../protocol/staking/candidate_center_test.go | 8 +- .../staking/candidate_statemanager.go | 18 +- .../protocol/staking/candidate_statereader.go | 86 ++- action/protocol/staking/candidate_test.go | 6 +- .../staking/contractstake_bucket_type.go | 56 +- .../protocol/staking/contractstake_indexer.go | 7 +- .../staking/contractstake_indexer_mock.go | 33 +- .../staking/contractstake_indexer_test.go | 6 +- .../staking/contractstaking/bucket.go | 112 ++++ .../staking/contractstaking/bucket_type.go | 67 +++ .../staking/contractstaking/contract.go | 52 ++ .../staking/contractstaking/statemanager.go | 65 +++ .../contractstaking/statemanager_test.go | 183 +++++++ .../staking/contractstaking/statereader.go | 149 ++++++ .../contractstaking/statereader_test.go | 138 +++++ .../handler_candidate_endorsement_test.go | 6 +- .../handler_candidate_selfstake_test.go | 4 +- .../handler_candidate_transfer_ownership.go | 2 +- ...ndler_candidate_transfer_ownership_test.go | 2 +- .../staking/handler_stake_migrate_test.go | 4 +- action/protocol/staking/handlers.go | 4 +- action/protocol/staking/handlers_test.go | 86 +-- action/protocol/staking/protocol.go | 82 +-- action/protocol/staking/protocol_test.go | 13 +- action/protocol/staking/read_state.go | 2 +- action/protocol/staking/stakeview_builder.go | 4 +- .../protocol/staking/staking_statereader.go | 2 +- .../staking/staking_statereader_test.go | 2 +- .../protocol/staking/stakingpb/staking.pb.go | 499 ++++++++++++------ .../protocol/staking/stakingpb/staking.proto | 15 + action/protocol/staking/util.go | 39 ++ action/protocol/staking/viewdata.go | 66 ++- action/protocol/staking/viewdata_test.go | 42 +- action/protocol/staking/vote_bucket_test.go | 14 +- action/protocol/staking/vote_reviser.go | 4 +- action/protocol/staking/vote_reviser_test.go | 6 +- blockindex/contractstaking/bucket_info.go | 5 +- blockindex/contractstaking/cache.go | 81 ++- blockindex/contractstaking/cache_test.go | 13 +- blockindex/contractstaking/dirty_cache.go | 24 +- .../contractstaking/dirty_cache_test.go | 9 +- blockindex/contractstaking/event_handler.go | 18 + blockindex/contractstaking/indexer.go | 145 +++-- blockindex/contractstaking/indexer_test.go | 62 +-- blockindex/contractstaking/stakeview.go | 96 +++- blockindex/contractstaking/wrappedcache.go | 29 +- chainservice/builder.go | 12 +- state/factory/statedb.go | 11 +- state/factory/util.go | 4 +- state/factory/workingset.go | 6 + state/factory/workingset_test.go | 2 +- systemcontractindex/common.go | 7 +- systemcontractindex/stakingindex/bucket.go | 16 +- .../stakingindex/bucket_test.go | 114 ++-- systemcontractindex/stakingindex/cache.go | 42 +- .../stakingindex/cache_test.go | 74 +++ .../stakingindex/event_handler.go | 89 +++- systemcontractindex/stakingindex/index.go | 161 +++--- systemcontractindex/stakingindex/stakeview.go | 76 ++- .../stakingindex/wrappedcache.go | 25 +- 66 files changed, 2212 insertions(+), 825 deletions(-) create mode 100644 action/protocol/staking/contractstaking/bucket.go create mode 100644 action/protocol/staking/contractstaking/bucket_type.go create mode 100644 action/protocol/staking/contractstaking/contract.go create mode 100644 action/protocol/staking/contractstaking/statemanager.go create mode 100644 action/protocol/staking/contractstaking/statemanager_test.go create mode 100644 action/protocol/staking/contractstaking/statereader.go create mode 100644 action/protocol/staking/contractstaking/statereader_test.go create mode 100644 action/protocol/staking/util.go create mode 100644 systemcontractindex/stakingindex/cache_test.go diff --git a/action/protocol/context.go b/action/protocol/context.go index 503e85aa6e..4153b97d7b 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -162,6 +162,7 @@ type ( NotSlashUnproductiveDelegates bool CandidateBLSPublicKey bool NotUseMinSelfStakeToBeActive bool + LoadContractStakingFromIndexer bool } // FeatureWithHeightCtx provides feature check functions. @@ -327,6 +328,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { NotSlashUnproductiveDelegates: !g.IsToBeEnabled(height), CandidateBLSPublicKey: g.IsToBeEnabled(height), NotUseMinSelfStakeToBeActive: !g.IsToBeEnabled(height), + LoadContractStakingFromIndexer: !g.IsToBeEnabled(height), }, ) } diff --git a/action/protocol/mock_protocol.go b/action/protocol/mock_protocol.go index a32ece6851..ebdf0ed9dd 100644 --- a/action/protocol/mock_protocol.go +++ b/action/protocol/mock_protocol.go @@ -503,7 +503,7 @@ func (mr *MockViewMockRecorder) Clone() *gomock.Call { } // Commit mocks base method. -func (m *MockView) Commit(arg0 context.Context, arg1 StateReader) error { +func (m *MockView) Commit(arg0 context.Context, arg1 StateManager) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Commit", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/action/protocol/protocol.go b/action/protocol/protocol.go index 7d8d4f4cca..85d59fa297 100644 --- a/action/protocol/protocol.go +++ b/action/protocol/protocol.go @@ -116,7 +116,7 @@ type ( Fork() View Snapshot() int Revert(int) error - Commit(context.Context, StateReader) error + Commit(context.Context, StateManager) error } // Views stores the view for all protocols @@ -139,9 +139,9 @@ func (views *Views) Fork() *Views { return fork } -func (views *Views) Commit(ctx context.Context, sr StateReader) error { +func (views *Views) Commit(ctx context.Context, sm StateManager) error { for _, view := range views.vm { - if err := view.Commit(ctx, sr); err != nil { + if err := view.Commit(ctx, sm); err != nil { return err } } diff --git a/action/protocol/staking/bucket_index_test.go b/action/protocol/staking/bucket_index_test.go index 0665811a0c..0762b31929 100644 --- a/action/protocol/staking/bucket_index_test.go +++ b/action/protocol/staking/bucket_index_test.go @@ -123,18 +123,18 @@ func TestGetPutBucketIndex(t *testing.T) { // put buckets and get for i, e := range tests { - _, _, err := csr.voterBucketIndices(e.voterAddr) + _, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) if i == 0 { require.Equal(state.ErrStateNotExist, errors.Cause(err)) } - _, _, err = csr.candBucketIndices(e.candAddr) + _, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) if i == 0 { require.Equal(state.ErrStateNotExist, errors.Cause(err)) } // put voter bucket index require.NoError(csm.putVoterBucketIndex(e.voterAddr, e.index)) - bis, _, err := csr.voterBucketIndices(e.voterAddr) + bis, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) require.NoError(err) bucketIndices := *bis require.Equal(e.voterIndexSize, len(bucketIndices)) @@ -142,7 +142,7 @@ func TestGetPutBucketIndex(t *testing.T) { // put candidate bucket index require.NoError(csm.putCandBucketIndex(e.candAddr, e.index)) - bis, _, err = csr.candBucketIndices(e.candAddr) + bis, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) require.NoError(err) bucketIndices = *bis require.Equal(e.candIndexSize, len(bucketIndices)) @@ -152,7 +152,7 @@ func TestGetPutBucketIndex(t *testing.T) { for _, e := range tests { // delete voter bucket index require.NoError(csm.delVoterBucketIndex(e.voterAddr, e.index)) - bis, _, err := csr.voterBucketIndices(e.voterAddr) + bis, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) if e.voterIndexSize != indexSize { bucketIndices := *bis require.Equal(indexSize-e.voterIndexSize, len(bucketIndices)) @@ -162,7 +162,7 @@ func TestGetPutBucketIndex(t *testing.T) { // delete candidate bucket index require.NoError(csm.delCandBucketIndex(e.candAddr, e.index)) - bis, _, err = csr.candBucketIndices(e.candAddr) + bis, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) if e.candIndexSize != indexSize { bucketIndices := *bis require.Equal(indexSize-e.candIndexSize, len(bucketIndices)) diff --git a/action/protocol/staking/bucket_pool.go b/action/protocol/staking/bucket_pool.go index 5deef073ad..a61d150296 100644 --- a/action/protocol/staking/bucket_pool.go +++ b/action/protocol/staking/bucket_pool.go @@ -146,7 +146,7 @@ func (bp *BucketPool) Clone() *BucketPool { } // Commit is called upon workingset commit -func (bp *BucketPool) Commit(sr protocol.StateReader) error { +func (bp *BucketPool) Commit() error { bp.dirty = false return nil } diff --git a/action/protocol/staking/candidate_center.go b/action/protocol/staking/candidate_center.go index e6ff939694..8df12e7ecc 100644 --- a/action/protocol/staking/candidate_center.go +++ b/action/protocol/staking/candidate_center.go @@ -126,8 +126,8 @@ func (m *CandidateCenter) commit() error { } // Commit writes the change into base -func (m *CandidateCenter) Commit(ctx context.Context, sr protocol.StateReader) error { - height, err := sr.Height() +func (m *CandidateCenter) Commit(ctx context.Context, sm protocol.StateManager) error { + height, err := sm.Height() if err != nil { return err } @@ -292,8 +292,8 @@ func (m *CandidateCenter) WriteToStateDB(sm protocol.StateManager) error { name := m.base.candsInNameMap() op := m.base.candsInOperatorMap() owners := m.base.ownersList() - if len(name) == 0 || len(op) == 0 { - return ErrNilParameters + if len(name) == 0 || len(op) == 0 || len(owners) == 0 { + return nil } if _, err := sm.PutState(name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { return err diff --git a/action/protocol/staking/candidate_center_test.go b/action/protocol/staking/candidate_center_test.go index e85d53096c..4c192528d9 100644 --- a/action/protocol/staking/candidate_center_test.go +++ b/action/protocol/staking/candidate_center_test.go @@ -325,7 +325,7 @@ func TestFixAlias(t *testing.T) { } else { r.NoError(m.commit()) } - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: m, }) @@ -376,7 +376,7 @@ func TestFixAlias(t *testing.T) { } else { r.NoError(center.commit()) } - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: center, }) } @@ -476,7 +476,7 @@ func TestMultipleNonStakingCandidate(t *testing.T) { r.True(testEqual(candcenter, CandidateList(cands))) // from state manager views := protocol.NewViews() - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: candcenter, }) candcenter = candCenterFromNewCandidateStateManager(r, views) @@ -538,7 +538,7 @@ func candCenterFromNewCandidateStateManager(r *require.Assertions, views *protoc // get cand center: csm.ConstructBaseView v, err := views.Read(_protocolID) r.NoError(err) - return v.(*ViewData).candCenter + return v.(*viewData).candCenter } func TestCandidateUpsert(t *testing.T) { diff --git a/action/protocol/staking/candidate_statemanager.go b/action/protocol/staking/candidate_statemanager.go index 5c99c119b4..25fc43c58f 100644 --- a/action/protocol/staking/candidate_statemanager.go +++ b/action/protocol/staking/candidate_statemanager.go @@ -40,10 +40,10 @@ type ( // CandidateStateManager is candidate state manager on top of StateManager CandidateStateManager interface { BucketSet - BucketGetByIndex + NativeBucketGetByIndex CandidateSet // candidate and bucket pool related - DirtyView() *ViewData + DirtyView() *viewData ContainsName(string) bool ContainsOwner(address.Address) bool ContainsOperator(address.Address) bool @@ -64,7 +64,7 @@ type ( ContainsSelfStakingBucket(uint64) bool GetByIdentifier(address.Address) *Candidate SR() protocol.StateReader - BucketGetByIndex + NativeBucketGetByIndex } candSM struct { @@ -108,15 +108,15 @@ func (csm *candSM) SR() protocol.StateReader { } // DirtyView is csm's current state, which reflects base view + applying delta saved in csm's dock -func (csm *candSM) DirtyView() *ViewData { +func (csm *candSM) DirtyView() *viewData { v, err := csm.StateManager.ReadView(_protocolID) if err != nil { log.S().Panic("failed to read view", zap.Error(err)) } - return &ViewData{ + return &viewData{ candCenter: csm.candCenter, bucketPool: csm.bucketPool, - contractsStake: v.(*ViewData).contractsStake, + contractsStake: v.(*viewData).contractsStake, } } @@ -175,12 +175,12 @@ func (csm *candSM) Commit(ctx context.Context) error { return csm.WriteView(_protocolID, view) } -func (csm *candSM) getBucket(index uint64) (*VoteBucket, error) { - return newCandidateStateReader(csm).getBucket(index) +func (csm *candSM) NativeBucket(index uint64) (*VoteBucket, error) { + return newCandidateStateReader(csm).NativeBucket(index) } func (csm *candSM) updateBucket(index uint64, bucket *VoteBucket) error { - if _, err := csm.getBucket(index); err != nil { + if _, err := csm.NativeBucket(index); err != nil { return err } diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index 15d95b1528..fd6505b1bd 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -20,24 +20,9 @@ import ( ) type ( - // BucketGetByIndex related to obtaining bucket by index - BucketGetByIndex interface { - getBucket(index uint64) (*VoteBucket, error) - } - // BucketGet related to obtaining bucket - BucketGet interface { - BucketGetByIndex - getTotalBucketCount() (uint64, error) - getAllBuckets() ([]*VoteBucket, uint64, error) - getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) - getBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) - voterBucketIndices(addr address.Address) (*BucketIndices, uint64, error) - candBucketIndices(addr address.Address) (*BucketIndices, uint64, error) - } - // CandidateGet related to obtaining Candidate - CandidateGet interface { - getCandidate(name address.Address) (*Candidate, uint64, error) - getAllCandidates() (CandidateList, uint64, error) + // NativeBucketGetByIndex related to obtaining bucket by index + NativeBucketGetByIndex interface { + NativeBucket(index uint64) (*VoteBucket, error) } // ReadState related to read bucket and candidate by request ReadState interface { @@ -53,12 +38,19 @@ type ( } // CandidateStateReader contains candidate center and bucket pool CandidateStateReader interface { - BucketGet - CandidateGet + NativeBucketGetByIndex + NumOfNativeBucket() (uint64, error) + NativeBuckets() ([]*VoteBucket, uint64, error) + NativeBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) + NativeBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) + NativeBucketIndicesByVoter(addr address.Address) (*BucketIndices, uint64, error) + NativeBucketIndicesByCandidate(addr address.Address) (*BucketIndices, uint64, error) + CandidateByAddress(name address.Address) (*Candidate, uint64, error) + getAllCandidates() (CandidateList, uint64, error) ReadState Height() uint64 SR() protocol.StateReader - BaseView() *ViewData + BaseView() *viewData NewBucketPool(enableSMStorage bool) (*BucketPool, error) GetCandidateByName(string) *Candidate GetCandidateByOwner(address.Address) *Candidate @@ -72,7 +64,7 @@ type ( candSR struct { protocol.StateReader height uint64 - view *ViewData + view *viewData } ) @@ -90,7 +82,7 @@ func (c *candSR) SR() protocol.StateReader { return c.StateReader } -func (c *candSR) BaseView() *ViewData { +func (c *candSR) BaseView() *viewData { return c.view } @@ -138,14 +130,14 @@ func ConstructBaseView(sr protocol.StateReader) (CandidateStateReader, error) { return nil, err } - view, ok := v.(*ViewData) + view, ok := v.(*viewData) if !ok { return nil, errors.Wrap(ErrTypeAssertion, "expecting *ViewData") } return &candSR{ StateReader: sr, height: height, - view: &ViewData{ + view: &viewData{ candCenter: view.candCenter, bucketPool: view.bucketPool, contractsStake: view.contractsStake, @@ -154,7 +146,7 @@ func ConstructBaseView(sr protocol.StateReader) (CandidateStateReader, error) { } // CreateBaseView creates the base view from state reader -func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*ViewData, uint64, error) { +func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*viewData, uint64, error) { if sr == nil { return nil, 0, ErrMissingField } @@ -175,13 +167,13 @@ func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*ViewData, u return nil, height, err } - return &ViewData{ + return &viewData{ candCenter: center, bucketPool: pool, }, height, nil } -func (c *candSR) getTotalBucketCount() (uint64, error) { +func (c *candSR) NumOfNativeBucket() (uint64, error) { var tc totalBucketCount _, err := c.State( &tc, @@ -190,7 +182,7 @@ func (c *candSR) getTotalBucketCount() (uint64, error) { return tc.count, err } -func (c *candSR) getBucket(index uint64) (*VoteBucket, error) { +func (c *candSR) NativeBucket(index uint64) (*VoteBucket, error) { var ( vb VoteBucket err error @@ -214,12 +206,12 @@ func (c *candSR) getBucket(index uint64) (*VoteBucket, error) { return &vb, nil } -func (c *candSR) getAllBuckets() ([]*VoteBucket, uint64, error) { +func (c *candSR) NativeBuckets() ([]*VoteBucket, uint64, error) { height, iter, err := c.States( protocol.NamespaceOption(_stakingNameSpace), protocol.KeysOption(func() ([][]byte, error) { // TODO (zhi): fix potential racing issue - count, err := c.getTotalBucketCount() + count, err := c.NumOfNativeBucket() if err != nil { return nil, err } @@ -249,10 +241,10 @@ func (c *candSR) getAllBuckets() ([]*VoteBucket, uint64, error) { return buckets, height, nil } -func (c *candSR) getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { +func (c *candSR) NativeBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { buckets := make([]*VoteBucket, 0, len(indices)) for _, i := range indices { - b, err := c.getBucket(i) + b, err := c.NativeBucket(i) if err != nil && err != ErrWithdrawnBucket { return buckets, err } @@ -266,7 +258,7 @@ func (c *candSR) getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, er func (c *candSR) getExistingBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { buckets := make([]*VoteBucket, 0, len(indices)) for _, i := range indices { - b, err := c.getBucket(i) + b, err := c.NativeBucket(i) if err != nil { if errors.Is(err, state.ErrStateNotExist) { continue @@ -278,7 +270,7 @@ func (c *candSR) getExistingBucketsWithIndices(indices BucketIndices) ([]*VoteBu return buckets, nil } -func (c *candSR) getBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) { +func (c *candSR) NativeBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) { var ( bis BucketIndices key = AddrKeyWithPrefix(addr, prefix) @@ -293,15 +285,15 @@ func (c *candSR) getBucketIndices(addr address.Address, prefix byte) (*BucketInd return &bis, height, nil } -func (c *candSR) voterBucketIndices(addr address.Address) (*BucketIndices, uint64, error) { - return c.getBucketIndices(addr, _voterIndex) +func (c *candSR) NativeBucketIndicesByVoter(addr address.Address) (*BucketIndices, uint64, error) { + return c.NativeBucketIndices(addr, _voterIndex) } -func (c *candSR) candBucketIndices(addr address.Address) (*BucketIndices, uint64, error) { - return c.getBucketIndices(addr, _candIndex) +func (c *candSR) NativeBucketIndicesByCandidate(addr address.Address) (*BucketIndices, uint64, error) { + return c.NativeBucketIndices(addr, _candIndex) } -func (c *candSR) getCandidate(name address.Address) (*Candidate, uint64, error) { +func (c *candSR) CandidateByAddress(name address.Address) (*Candidate, uint64, error) { if name == nil { return nil, 0, ErrNilParameters } @@ -347,7 +339,7 @@ func (c *candSR) NewBucketPool(enableSMStorage bool) (*BucketPool, error) { } // sum up all existing buckets - all, _, err := c.getAllBuckets() + all, _, err := c.NativeBuckets() if err != nil && errors.Cause(err) != state.ErrStateNotExist { return nil, err } @@ -363,7 +355,7 @@ func (c *candSR) NewBucketPool(enableSMStorage bool) (*BucketPool, error) { } func (c *candSR) readStateBuckets(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBuckets) (*iotextypes.VoteBucketList, uint64, error) { - all, height, err := c.getAllBuckets() + all, height, err := c.NativeBuckets() if err != nil { return nil, height, err } @@ -381,14 +373,14 @@ func (c *candSR) readStateBucketsByVoter(ctx context.Context, req *iotexapi.Read return nil, 0, err } - indices, height, err := c.voterBucketIndices(voter) + indices, height, err := c.NativeBucketIndicesByVoter(voter) if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.VoteBucketList{}, height, nil } if indices == nil || err != nil { return nil, height, err } - buckets, err := c.getBucketsWithIndices(*indices) + buckets, err := c.NativeBucketsWithIndices(*indices) if err != nil { return nil, height, err } @@ -406,14 +398,14 @@ func (c *candSR) readStateBucketsByCandidate(ctx context.Context, req *iotexapi. return &iotextypes.VoteBucketList{}, 0, nil } - indices, height, err := c.candBucketIndices(cand.GetIdentifier()) + indices, height, err := c.NativeBucketIndicesByCandidate(cand.GetIdentifier()) if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.VoteBucketList{}, height, nil } if indices == nil || err != nil { return nil, height, err } - buckets, err := c.getBucketsWithIndices(*indices) + buckets, err := c.NativeBucketsWithIndices(*indices) if err != nil { return nil, height, err } @@ -439,7 +431,7 @@ func (c *candSR) readStateBucketByIndices(ctx context.Context, req *iotexapi.Rea } func (c *candSR) readStateBucketCount(ctx context.Context, _ *iotexapi.ReadStakingDataRequest_BucketsCount) (*iotextypes.BucketsCount, uint64, error) { - total, err := c.getTotalBucketCount() + total, err := c.NumOfNativeBucket() if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.BucketsCount{}, c.Height(), nil } diff --git a/action/protocol/staking/candidate_test.go b/action/protocol/staking/candidate_test.go index 2715a372a5..56ddad626b 100644 --- a/action/protocol/staking/candidate_test.go +++ b/action/protocol/staking/candidate_test.go @@ -205,10 +205,10 @@ func TestGetPutCandidate(t *testing.T) { // put candidates and get for _, e := range testCandidates { - _, _, err := csr.getCandidate(e.d.Owner) + _, _, err := csr.CandidateByAddress(e.d.Owner) require.Equal(state.ErrStateNotExist, errors.Cause(err)) require.NoError(csm.putCandidate(e.d)) - d1, _, err := csr.getCandidate(e.d.Owner) + d1, _, err := csr.CandidateByAddress(e.d.Owner) require.NoError(err) require.Equal(e.d, d1) } @@ -229,7 +229,7 @@ func TestGetPutCandidate(t *testing.T) { // delete buckets and get for _, e := range testCandidates { require.NoError(csm.delCandidate(e.d.GetIdentifier())) - _, _, err := csr.getCandidate(e.d.Owner) + _, _, err := csr.CandidateByAddress(e.d.Owner) require.Equal(state.ErrStateNotExist, errors.Cause(err)) } } diff --git a/action/protocol/staking/contractstake_bucket_type.go b/action/protocol/staking/contractstake_bucket_type.go index 83cfb1ee9b..53b5534c89 100644 --- a/action/protocol/staking/contractstake_bucket_type.go +++ b/action/protocol/staking/contractstake_bucket_type.go @@ -6,62 +6,10 @@ package staking import ( - "math/big" - - "github.com/pkg/errors" - "google.golang.org/protobuf/proto" - - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type ( // ContractStakingBucketType defines the type of contract staking bucket - ContractStakingBucketType struct { - Amount *big.Int - Duration uint64 // block numbers - ActivatedAt uint64 // block height - } + ContractStakingBucketType = contractstaking.BucketType ) - -// Serialize serializes the bucket type -func (bt *ContractStakingBucketType) Serialize() []byte { - return byteutil.Must(proto.Marshal(bt.toProto())) -} - -// Deserialize deserializes the bucket type -func (bt *ContractStakingBucketType) Deserialize(b []byte) error { - m := stakingpb.BucketType{} - if err := proto.Unmarshal(b, &m); err != nil { - return err - } - return bt.loadProto(&m) -} - -// Clone clones the bucket type -func (bt *ContractStakingBucketType) Clone() *ContractStakingBucketType { - return &ContractStakingBucketType{ - Amount: big.NewInt(0).Set(bt.Amount), - Duration: bt.Duration, - ActivatedAt: bt.ActivatedAt, - } -} - -func (bt *ContractStakingBucketType) toProto() *stakingpb.BucketType { - return &stakingpb.BucketType{ - Amount: bt.Amount.String(), - Duration: bt.Duration, - ActivatedAt: bt.ActivatedAt, - } -} - -func (bt *ContractStakingBucketType) loadProto(p *stakingpb.BucketType) error { - amount, ok := big.NewInt(0).SetString(p.Amount, 10) - if !ok { - return errors.New("failed to parse amount") - } - bt.Amount = amount - bt.Duration = p.Duration - bt.ActivatedAt = p.ActivatedAt - return nil -} diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index b293494925..4d8568389e 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) var ( @@ -37,9 +38,9 @@ type ( // TotalBucketCount returns the total number of buckets including burned buckets TotalBucketCount(height uint64) (uint64, error) // ContractAddress returns the contract address - ContractAddress() string - // StartView returns the contract stake view - StartView(ctx context.Context) (ContractStakeView, error) + ContractAddress() address.Address + // LoadStakeView loads the contract stake view from state reader + LoadStakeView(context.Context, protocol.StateReader) (ContractStakeView, error) } // ContractStakingIndexerWithBucketType defines the interface of contract staking reader with bucket type ContractStakingIndexerWithBucketType interface { diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index eb2f9a3ff9..1c049bc48d 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -14,6 +14,7 @@ import ( reflect "reflect" address "github.com/iotexproject/iotex-address/address" + protocol "github.com/iotexproject/iotex-core/v2/action/protocol" gomock "go.uber.org/mock/gomock" ) @@ -87,10 +88,10 @@ func (mr *MockContractStakingIndexerMockRecorder) BucketsByIndices(arg0, arg1 an } // ContractAddress mocks base method. -func (m *MockContractStakingIndexer) ContractAddress() string { +func (m *MockContractStakingIndexer) ContractAddress() address.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ContractAddress") - ret0, _ := ret[0].(string) + ret0, _ := ret[0].(address.Address) return ret0 } @@ -115,19 +116,19 @@ func (mr *MockContractStakingIndexerMockRecorder) Height() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexer)(nil).Height)) } -// StartView mocks base method. -func (m *MockContractStakingIndexer) StartView(ctx context.Context) (ContractStakeView, error) { +// LoadStakeView mocks base method. +func (m *MockContractStakingIndexer) LoadStakeView(arg0 context.Context, arg1 protocol.StateReader) (ContractStakeView, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartView", ctx) + ret := m.ctrl.Call(m, "LoadStakeView", arg0, arg1) ret0, _ := ret[0].(ContractStakeView) ret1, _ := ret[1].(error) return ret0, ret1 } -// StartView indicates an expected call of StartView. -func (mr *MockContractStakingIndexerMockRecorder) StartView(ctx any) *gomock.Call { +// LoadStakeView indicates an expected call of LoadStakeView. +func (mr *MockContractStakingIndexerMockRecorder) LoadStakeView(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartView", reflect.TypeOf((*MockContractStakingIndexer)(nil).StartView), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadStakeView", reflect.TypeOf((*MockContractStakingIndexer)(nil).LoadStakeView), arg0, arg1) } // TotalBucketCount mocks base method. @@ -230,10 +231,10 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) BucketsByIndices } // ContractAddress mocks base method. -func (m *MockContractStakingIndexerWithBucketType) ContractAddress() string { +func (m *MockContractStakingIndexerWithBucketType) ContractAddress() address.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ContractAddress") - ret0, _ := ret[0].(string) + ret0, _ := ret[0].(address.Address) return ret0 } @@ -258,19 +259,19 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) Height() *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).Height)) } -// StartView mocks base method. -func (m *MockContractStakingIndexerWithBucketType) StartView(ctx context.Context) (ContractStakeView, error) { +// LoadStakeView mocks base method. +func (m *MockContractStakingIndexerWithBucketType) LoadStakeView(arg0 context.Context, arg1 protocol.StateReader) (ContractStakeView, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartView", ctx) + ret := m.ctrl.Call(m, "LoadStakeView", arg0, arg1) ret0, _ := ret[0].(ContractStakeView) ret1, _ := ret[1].(error) return ret0, ret1 } -// StartView indicates an expected call of StartView. -func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) StartView(ctx any) *gomock.Call { +// LoadStakeView indicates an expected call of LoadStakeView. +func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) LoadStakeView(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartView", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).StartView), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadStakeView", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).LoadStakeView), arg0, arg1) } // TotalBucketCount mocks base method. diff --git a/action/protocol/staking/contractstake_indexer_test.go b/action/protocol/staking/contractstake_indexer_test.go index 85cf317174..d862ba8014 100644 --- a/action/protocol/staking/contractstake_indexer_test.go +++ b/action/protocol/staking/contractstake_indexer_test.go @@ -83,10 +83,12 @@ func TestDelayTolerantIndexer(t *testing.T) { count, err := delayIndexer.TotalBucketCount(delayHeight) r.NoError(err) r.Equal(uint64(len(indexerBuckets)), count) + addr, err := address.FromString(indexerAddress) + r.NoError(err) // ContractAddress - indexer.EXPECT().ContractAddress().Return(indexerAddress).AnyTimes() + indexer.EXPECT().ContractAddress().Return(addr).AnyTimes() ca := delayIndexer.ContractAddress() - r.Equal(indexerAddress, ca) + r.Equal(indexerAddress, ca.String()) // BucketTypes indexer.EXPECT().BucketTypes(gomock.Any()).DoAndReturn(func(height uint64) ([]*ContractStakingBucketType, error) { if height <= atomic.LoadUint64(&indexerHeight) { diff --git a/action/protocol/staking/contractstaking/bucket.go b/action/protocol/staking/contractstaking/bucket.go new file mode 100644 index 0000000000..91429ed37c --- /dev/null +++ b/action/protocol/staking/contractstaking/bucket.go @@ -0,0 +1,112 @@ +package contractstaking + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +type ( + // Bucket is the structure that holds information about a staking bucket. + Bucket struct { + // Candidate is the address of the candidate that this bucket is staking for. + Candidate address.Address + // Owner is the address of the owner of this bucket. + Owner address.Address + // StakedAmount is the amount of tokens staked in this bucket. + StakedAmount *big.Int + + // StakedDuration is the duration for which the tokens have been staked. + StakedDuration uint64 // in seconds if timestamped, in block number if not + // CreatedAt is the time when the bucket was created. + CreatedAt uint64 // in unix timestamp if timestamped, in block height if not + // UnlockedAt is the time when the bucket can be unlocked. + UnlockedAt uint64 // in unix timestamp if timestamped, in block height if not + // UnstakedAt is the time when the bucket was unstaked. + UnstakedAt uint64 // in unix timestamp if timestamped, in block height if not + + // IsTimestampBased indicates whether the bucket is timestamp-based + IsTimestampBased bool + // Muted indicates whether the bucket is vote weight muted + Muted bool + } +) + +func (b *Bucket) toProto() *stakingpb.SystemStakingBucket { + if b == nil { + return nil + } + return &stakingpb.SystemStakingBucket{ + Owner: b.Owner.Bytes(), + Candidate: b.Candidate.Bytes(), + Amount: b.StakedAmount.Bytes(), + Duration: b.StakedDuration, + CreatedAt: b.CreatedAt, + UnlockedAt: b.UnlockedAt, + UnstakedAt: b.UnstakedAt, + Muted: b.Muted, + } +} + +// LoadBucketFromProto converts a protobuf representation of a staking bucket to a Bucket struct. +func LoadBucketFromProto(pb *stakingpb.SystemStakingBucket) (*Bucket, error) { + if pb == nil { + return nil, nil + } + b := &Bucket{} + owner, err := address.FromBytes(pb.Owner) + if err != nil { + return nil, errors.Wrap(err, "failed to convert owner bytes to address") + } + b.Owner = owner + cand, err := address.FromBytes(pb.Candidate) + if err != nil { + return nil, errors.Wrap(err, "failed to convert candidate bytes to address") + } + b.Candidate = cand + b.StakedAmount = new(big.Int).SetBytes(pb.Amount) + b.StakedDuration = pb.Duration + b.CreatedAt = pb.CreatedAt + b.UnlockedAt = pb.UnlockedAt + b.UnstakedAt = pb.UnstakedAt + b.Muted = pb.Muted + + return b, nil +} + +// Serialize serializes the bucket to a byte slice. +func (b *Bucket) Serialize() ([]byte, error) { + return proto.Marshal(b.toProto()) +} + +// Deserialize deserializes the bucket from a byte slice. +func (b *Bucket) Deserialize(data []byte) error { + m := stakingpb.SystemStakingBucket{} + if err := proto.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, "failed to unmarshal bucket data") + } + bucket, err := LoadBucketFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load bucket from proto") + } + *b = *bucket + return nil +} + +// Clone creates a deep copy of the Bucket. +func (b *Bucket) Clone() *Bucket { + return &Bucket{ + Candidate: b.Candidate, + Owner: b.Owner, + StakedAmount: new(big.Int).Set(b.StakedAmount), + StakedDuration: b.StakedDuration, + CreatedAt: b.CreatedAt, + UnlockedAt: b.UnlockedAt, + UnstakedAt: b.UnstakedAt, + IsTimestampBased: b.IsTimestampBased, + Muted: b.Muted, + } +} diff --git a/action/protocol/staking/contractstaking/bucket_type.go b/action/protocol/staking/contractstaking/bucket_type.go new file mode 100644 index 0000000000..489f4245df --- /dev/null +++ b/action/protocol/staking/contractstaking/bucket_type.go @@ -0,0 +1,67 @@ +package contractstaking + +import ( + "math/big" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +type ( + // BucketType defines the type of contract staking bucket + BucketType struct { + Amount *big.Int + Duration uint64 + ActivatedAt uint64 + } +) + +func (bt *BucketType) toProto() *stakingpb.BucketType { + return &stakingpb.BucketType{ + Amount: bt.Amount.String(), + Duration: bt.Duration, + ActivatedAt: bt.ActivatedAt, + } +} + +// LoadBucketTypeFromProto converts a protobuf representation of a staking bucket type to a BucketType struct. +func LoadBucketTypeFromProto(pb *stakingpb.BucketType) (*BucketType, error) { + bt := &BucketType{} + amount, ok := new(big.Int).SetString(pb.Amount, 10) + if !ok { + return nil, errors.New("failed to parse amount from string") + } + bt.Amount = amount + bt.Duration = pb.Duration + bt.ActivatedAt = pb.ActivatedAt + return bt, nil +} + +// Serialize serializes the bucket type +func (bt *BucketType) Serialize() ([]byte, error) { + return proto.Marshal(bt.toProto()) +} + +// Deserialize deserializes the bucket type +func (bt *BucketType) Deserialize(b []byte) error { + m := stakingpb.BucketType{} + if err := proto.Unmarshal(b, &m); err != nil { + return err + } + loaded, err := LoadBucketTypeFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load bucket type from proto") + } + *bt = *loaded + return nil +} + +// Clone clones the bucket type +func (bt *BucketType) Clone() *BucketType { + return &BucketType{ + Amount: big.NewInt(0).Set(bt.Amount), + Duration: bt.Duration, + ActivatedAt: bt.ActivatedAt, + } +} diff --git a/action/protocol/staking/contractstaking/contract.go b/action/protocol/staking/contractstaking/contract.go new file mode 100644 index 0000000000..81bfb1ac60 --- /dev/null +++ b/action/protocol/staking/contractstaking/contract.go @@ -0,0 +1,52 @@ +package contractstaking + +import ( + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +// StakingContract represents the staking contract in the system +type StakingContract struct { + // NumOfBuckets is the number of buckets in the staking contract + NumOfBuckets uint64 +} + +func (sc *StakingContract) toProto() *stakingpb.SystemStakingContract { + if sc == nil { + return nil + } + return &stakingpb.SystemStakingContract{ + NumOfBuckets: sc.NumOfBuckets, + } +} + +// LoadStakingContractFromProto converts a protobuf representation of a staking contract to a StakingContract struct. +func LoadStakingContractFromProto(pb *stakingpb.SystemStakingContract) (*StakingContract, error) { + if pb == nil { + return nil, nil + } + sc := &StakingContract{ + NumOfBuckets: pb.NumOfBuckets, + } + return sc, nil +} + +// Serialize serializes the staking contract +func (sc *StakingContract) Serialize() ([]byte, error) { + return proto.Marshal(sc.toProto()) +} + +// Deserialize deserializes the staking contract +func (sc *StakingContract) Deserialize(b []byte) error { + m := stakingpb.SystemStakingContract{} + if err := proto.Unmarshal(b, &m); err != nil { + return err + } + loaded, err := LoadStakingContractFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load staking contract from proto") + } + *sc = *loaded + return nil +} diff --git a/action/protocol/staking/contractstaking/statemanager.go b/action/protocol/staking/contractstaking/statemanager.go new file mode 100644 index 0000000000..42964cb90e --- /dev/null +++ b/action/protocol/staking/contractstaking/statemanager.go @@ -0,0 +1,65 @@ +package contractstaking + +import ( + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" +) + +// ContractStakingStateManager wraps a state manager to provide staking contract-specific writes. +type ContractStakingStateManager struct { + ContractStakingStateReader + sm protocol.StateManager +} + +// NewContractStakingStateManager creates a new ContractStakingStateManager +func NewContractStakingStateManager(sm protocol.StateManager) *ContractStakingStateManager { + return &ContractStakingStateManager{ + ContractStakingStateReader: ContractStakingStateReader{sr: sm}, + sm: sm, + } +} + +// UpsertBucketType inserts or updates a bucket type for a given contract and bucket ID. +func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Address, bucketID uint64, bucketType *BucketType) error { + _, err := cs.sm.PutState( + bucketType, + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ) + + return err +} + +// DeleteBucket removes a bucket for a given contract and bucket ID. +func (cs *ContractStakingStateManager) DeleteBucket(contractAddr address.Address, bucketID uint64) error { + _, err := cs.sm.DelState( + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ) + + return err +} + +// UpsertBucket inserts or updates a bucket for a given contract and bid. +func (cs *ContractStakingStateManager) UpsertBucket(contractAddr address.Address, bid uint64, bucket *Bucket) error { + _, err := cs.sm.PutState( + bucket, + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bid), + ) + + return err +} + +// UpdateNumOfBuckets updates the number of buckets. +func (cs *ContractStakingStateManager) UpdateNumOfBuckets(contractAddr address.Address, numOfBuckets uint64) error { + _, err := cs.sm.PutState( + &StakingContract{ + NumOfBuckets: uint64(numOfBuckets), + }, + metaNamespaceOption(), + contractKeyOption(contractAddr), + ) + + return err +} diff --git a/action/protocol/staking/contractstaking/statemanager_test.go b/action/protocol/staking/contractstaking/statemanager_test.go new file mode 100644 index 0000000000..c7714a6fcd --- /dev/null +++ b/action/protocol/staking/contractstaking/statemanager_test.go @@ -0,0 +1,183 @@ +package contractstaking + +import ( + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" +) + +// TestNewContractStakingStateManager tests the creation of a new ContractStakingStateManager. +func TestNewContractStakingStateManager(t *testing.T) { + mockSM := &mock_chainmanager.MockStateManager{} + csm := NewContractStakingStateManager(mockSM) + assert.NotNil(t, csm) + assert.Equal(t, mockSM, csm.sm) +} + +// TestUpsertBucketType_Success tests the successful insertion of a bucket type. +func TestUpsertBucketType_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bucketID := uint64(123) + bucketType := &BucketType{ + Amount: big.NewInt(1000), + Duration: 3600, + ActivatedAt: 1622547800, + } + + err := csm.UpsertBucketType(contractAddr, bucketID, bucketType) + require.NoError(t, err) +} + +// TestUpsertBucketType_Error tests the error case for inserting a bucket type. +func TestUpsertBucketType_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("putstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + bucketType := &BucketType{ + Amount: big.NewInt(2000), + Duration: 7200, + ActivatedAt: 1622548100, + } + + err := csm.UpsertBucketType(contractAddr, bucketID, bucketType) + assert.Error(t, err) + assert.Equal(t, "putstate error", err.Error()) +} + +func TestDeleteBucket_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("delstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + + err := csm.DeleteBucket(contractAddr, bucketID) + assert.Error(t, err) + assert.Equal(t, "delstate error", err.Error()) +} + +// TestDeleteBucket_Success tests the successful deletion of a bucket. +func TestDeleteBucket_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + + err := csm.DeleteBucket(contractAddr, bucketID) + require.NoError(t, err) +} + +// TestUpsertBucket_Success tests the successful insertion of a bucket. +func TestUpsertBucket_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bid := uint64(123) + bucket := &Bucket{ + Owner: identityset.Address(1), + Candidate: identityset.Address(2), + StakedAmount: big.NewInt(1000), + StakedDuration: 3600, + CreatedAt: 1622547800, + UnlockedAt: 1622547900, + UnstakedAt: 1622548000, + Muted: false, + } + + err := csm.UpsertBucket(contractAddr, bid, bucket) + require.NoError(t, err) +} + +// TestUpsertBucket_Error tests the error case for inserting a bucket. +func TestUpsertBucket_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("putstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bid := uint64(456) + bucket := &Bucket{ + Owner: identityset.Address(3), + Candidate: identityset.Address(4), + StakedAmount: big.NewInt(2000), + StakedDuration: 7200, + CreatedAt: 1622548100, + UnlockedAt: 1622548200, + UnstakedAt: 1622548300, + Muted: true, + } + + err := csm.UpsertBucket(contractAddr, bid, bucket) + assert.Error(t, err) + assert.Equal(t, "putstate error", err.Error()) +} + +// TestUpdateContractMeta_Success tests the successful update of contract metadata. +func TestUpdateContractMeta_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(5) + numOfBuckets := uint64(10) + + err := csm.UpdateNumOfBuckets(contractAddr, numOfBuckets) + require.NoError(t, err) +} + +// TestUpdateContractMeta_Error tests the error case for updating contract metadata. +func TestUpdateContractMeta_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("meta error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(6) + numOfBuckets := uint64(20) + + err := csm.UpdateNumOfBuckets(contractAddr, numOfBuckets) + assert.Error(t, err) + assert.Equal(t, "meta error", err.Error()) +} diff --git a/action/protocol/staking/contractstaking/statereader.go b/action/protocol/staking/contractstaking/statereader.go new file mode 100644 index 0000000000..3058b07c85 --- /dev/null +++ b/action/protocol/staking/contractstaking/statereader.go @@ -0,0 +1,149 @@ +package contractstaking + +import ( + "fmt" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-address/address" +) + +// ContractStakingStateReader wraps a state reader to provide staking contract-specific reads. +type ContractStakingStateReader struct { + sr protocol.StateReader +} + +// NewStateReader creates a new ContractStakingStateReader. +func NewStateReader(sr protocol.StateReader) *ContractStakingStateReader { + return &ContractStakingStateReader{ + sr: sr, + } +} + +func contractNamespaceOption(contractAddr address.Address) protocol.StateOption { + return protocol.NamespaceOption(fmt.Sprintf("cs_bucket_%x", contractAddr.Bytes())) +} + +func bucketTypeNamespaceOption(contractAddr address.Address) protocol.StateOption { + return protocol.NamespaceOption(fmt.Sprintf("cs_bucket_type_%x", contractAddr.Bytes())) +} + +func contractKeyOption(contractAddr address.Address) protocol.StateOption { + return protocol.KeyOption(contractAddr.Bytes()) +} + +func bucketIDKeyOption(bucketID uint64) protocol.StateOption { + return protocol.KeyOption(byteutil.Uint64ToBytes(bucketID)) +} + +// metaNamespaceOption is the namespace for meta information (e.g., total number of buckets). +func metaNamespaceOption() protocol.StateOption { + return protocol.NamespaceOption("staking_contract_meta") +} + +func (r *ContractStakingStateReader) contract(contractAddr address.Address) (*StakingContract, error) { + var contract StakingContract + _, err := r.sr.State( + &contract, + metaNamespaceOption(), + contractKeyOption(contractAddr), + ) + if err != nil { + return nil, err + } + return &contract, nil +} + +// NumOfBuckets returns the total number of buckets for a contract. +func (r *ContractStakingStateReader) NumOfBuckets(contractAddr address.Address) (uint64, error) { + contract, err := r.contract(contractAddr) + if err != nil { + return 0, err + } + return contract.NumOfBuckets, nil +} + +// BucketType returns the BucketType for a given contract and bucket id. +func (r *ContractStakingStateReader) BucketType(contractAddr address.Address, tID uint64) (*BucketType, error) { + var bktType stakingpb.BucketType + if _, err := r.sr.State( + &bktType, + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(tID), + ); err != nil { + return nil, fmt.Errorf("failed to get bucket type %d for contract %s: %w", tID, contractAddr.String(), err) + } + return LoadBucketTypeFromProto(&bktType) +} + +// Bucket returns the Bucket for a given contract and bucket id. +func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucketID uint64) (*Bucket, error) { + var ssb Bucket + if _, err := r.sr.State( + &ssb, + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ); err != nil { + return nil, err + } + + return &ssb, nil +} + +// BucketTypes returns all BucketType for a given contract and bucket id. +func (r *ContractStakingStateReader) BucketTypes(contractAddr address.Address) ([]uint64, []*BucketType, error) { + _, iter, err := r.sr.States(bucketTypeNamespaceOption(contractAddr)) + if err != nil { + return nil, nil, fmt.Errorf("failed to get bucket types for contract %s: %w", contractAddr.String(), err) + } + ids := make([]uint64, 0, iter.Size()) + types := make([]*BucketType, 0, iter.Size()) + for i := 0; i < iter.Size(); i++ { + var bktType stakingpb.BucketType + switch key, err := iter.Next(&bktType); err { + case nil: + bt, err := LoadBucketTypeFromProto(&bktType) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to load bucket type from proto") + } + ids = append(ids, byteutil.BytesToUint64(key)) + types = append(types, bt) + case state.ErrNilValue: + default: + return nil, nil, fmt.Errorf("failed to read bucket type %d for contract %s: %w", byteutil.BytesToUint64(key), contractAddr.String(), err) + } + } + return ids, types, nil +} + +// Buckets returns all BucketInfo for a given contract. +func (r *ContractStakingStateReader) Buckets(contractAddr address.Address) ([]uint64, []*Bucket, error) { + _, iter, err := r.sr.States(contractNamespaceOption(contractAddr)) + if err != nil { + return nil, nil, fmt.Errorf("failed to get buckets for contract %s: %w", contractAddr.String(), err) + } + ids := make([]uint64, 0, iter.Size()) + buckets := make([]*Bucket, 0, iter.Size()) + for i := 0; i < iter.Size(); i++ { + var ssb stakingpb.SystemStakingBucket + switch key, err := iter.Next(&ssb); err { + case nil: + bucket, err := LoadBucketFromProto(&ssb) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to load bucket from proto") + } + if bucket != nil { + ids = append(ids, byteutil.BytesToUint64(key)) + buckets = append(buckets, bucket) + } + case state.ErrNilValue: + default: + return nil, nil, fmt.Errorf("failed to read bucket %d for contract %s: %w", byteutil.BytesToUint64(key), contractAddr.String(), err) + } + } + return ids, buckets, nil +} diff --git a/action/protocol/staking/contractstaking/statereader_test.go b/action/protocol/staking/contractstaking/statereader_test.go new file mode 100644 index 0000000000..8f607c844e --- /dev/null +++ b/action/protocol/staking/contractstaking/statereader_test.go @@ -0,0 +1,138 @@ +package contractstaking + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" +) + +func TestNewContractStateReader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + require.NotNil(t, reader) + require.Equal(t, mockSR, reader.sr) +} + +func TestNumOfBuckets(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + expected := &StakingContract{NumOfBuckets: 10} + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(s interface{}, _ ...protocol.StateOption) (uint64, error) { + *(s.(*StakingContract)) = *expected + return 0, nil + }, + ) + num, err := reader.NumOfBuckets(addr) + require.NoError(t, err) + require.Equal(t, uint64(10), num) +} + +func TestNumOfBuckets_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("err")) + num, err := reader.NumOfBuckets(addr) + require.Error(t, err) + require.Equal(t, uint64(0), num) +} + +func TestBucket(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + bucketID := uint64(123) + ssb := &Bucket{} + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(s interface{}, _ ...protocol.StateOption) (uint64, error) { + *(s.(*Bucket)) = *ssb + return 0, nil + }, + ) + + bucket, err := reader.Bucket(addr, bucketID) + require.NoError(t, err) + require.NotNil(t, bucket) +} + +func TestBucket_ErrorOnState(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + bucketID := uint64(123) + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("err")) + bucket, err := reader.Bucket(addr, bucketID) + require.Error(t, err) + require.Nil(t, bucket) +} + +type dummyIter struct { + size int + idx int + keys [][]byte +} + +func (d *dummyIter) Size() int { return d.size } +func (d *dummyIter) Next(s interface{}) ([]byte, error) { + if d.idx >= d.size { + return nil, state.ErrNilValue + } + *(s.(*stakingpb.SystemStakingBucket)) = stakingpb.SystemStakingBucket{} + key := d.keys[d.idx] + d.idx++ + return key, nil +} + +func TestBuckets(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + iter := &dummyIter{size: 2, keys: [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}, {2, 3, 4, 5, 6, 7, 8, 9}}} + mockSR.EXPECT().States(gomock.Any()).Return(uint64(0), iter, nil) + + ids, buckets, err := reader.Buckets(addr) + require.NoError(t, err) + require.Len(t, ids, 2) + require.Len(t, buckets, 2) +} + +func TestBuckets_StatesError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + mockSR.EXPECT().States(gomock.Any()).Return(uint64(0), nil, errors.New("states error")) + ids, buckets, err := reader.Buckets(addr) + require.Error(t, err) + require.Nil(t, ids) + require.Nil(t, buckets) +} diff --git a/action/protocol/staking/handler_candidate_endorsement_test.go b/action/protocol/staking/handler_candidate_endorsement_test.go index 4dd3516b07..9e26abcea3 100644 --- a/action/protocol/staking/handler_candidate_endorsement_test.go +++ b/action/protocol/staking/handler_candidate_endorsement_test.go @@ -347,7 +347,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) esm := NewEndorsementStateManager(csm.SM()) - bucket, err := csm.getBucket(1) + bucket, err := csm.NativeBucket(1) require.NoError(err) endorsement, err := esm.Get(bucket.Index) require.NoError(err) @@ -385,7 +385,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) esm := NewEndorsementStateManager(csm.SM()) - bucket, err := csm.getBucket(1) + bucket, err := csm.NativeBucket(1) require.NoError(err) endorsement, err := esm.Get(bucket.Index) require.NoError(err) @@ -573,7 +573,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { // check buckets esm := NewEndorsementStateManager(csm.SM()) for _, expectBkt := range test.expectBuckets { - bkt, err := csm.getBucket(expectBkt.id) + bkt, err := csm.NativeBucket(expectBkt.id) require.NoError(err) require.Equal(expectBkt.candidate, bkt.Candidate) endorse, err := esm.Get(expectBkt.id) diff --git a/action/protocol/staking/handler_candidate_selfstake_test.go b/action/protocol/staking/handler_candidate_selfstake_test.go index 52f808109b..8ca0d334ce 100644 --- a/action/protocol/staking/handler_candidate_selfstake_test.go +++ b/action/protocol/staking/handler_candidate_selfstake_test.go @@ -161,7 +161,7 @@ func initTestStateWithHeight(t *testing.T, ctrl *gomock.Controller, bucketCfgs [ ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) @@ -474,7 +474,7 @@ func TestProtocol_HandleCandidateSelfStake(t *testing.T) { } // check buckets for _, expectBkt := range test.expectBuckets { - bkt, err := csm.getBucket(expectBkt.id) + bkt, err := csm.NativeBucket(expectBkt.id) require.NoError(err) require.Equal(expectBkt.candidate, bkt.Candidate) } diff --git a/action/protocol/staking/handler_candidate_transfer_ownership.go b/action/protocol/staking/handler_candidate_transfer_ownership.go index c444a4f6b1..247ca8e874 100644 --- a/action/protocol/staking/handler_candidate_transfer_ownership.go +++ b/action/protocol/staking/handler_candidate_transfer_ownership.go @@ -39,7 +39,7 @@ func (p *Protocol) handleCandidateTransferOwnership(ctx context.Context, act *ac candidate.Owner = act.NewOwner() // clear selfstake needClear := func() (bool, *big.Int, error) { - bucket, err := csm.getBucket(candidate.SelfStakeBucketIdx) + bucket, err := csm.NativeBucket(candidate.SelfStakeBucketIdx) if err == nil { // keep the self-stake bucket if it's endorse bucket esm := NewEndorsementStateReader(csm.SR()) diff --git a/action/protocol/staking/handler_candidate_transfer_ownership_test.go b/action/protocol/staking/handler_candidate_transfer_ownership_test.go index 1d0861418a..9731caff3f 100644 --- a/action/protocol/staking/handler_candidate_transfer_ownership_test.go +++ b/action/protocol/staking/handler_candidate_transfer_ownership_test.go @@ -80,7 +80,7 @@ func TestProtocol_HandleCandidateTransferOwnership(t *testing.T) { ctx = protocol.WithFeatureWithHeightCtx(ctx) vv, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := vv.(*ViewData) + cc, ok := vv.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) diff --git a/action/protocol/staking/handler_stake_migrate_test.go b/action/protocol/staking/handler_stake_migrate_test.go index 0521780358..0b98ca9fb1 100644 --- a/action/protocol/staking/handler_stake_migrate_test.go +++ b/action/protocol/staking/handler_stake_migrate_test.go @@ -286,7 +286,7 @@ func TestHandleStakeMigrate(t *testing.T) { csm, err := NewCandidateStateManager(sm) r.NoError(err) preVotes := csm.GetByOwner(identityset.Address(candOwnerID)).Votes - bkt, err := csm.getBucket(bktIdx) + bkt, err := csm.NativeBucket(bktIdx) r.NoError(err) receipt := &action.Receipt{ Status: uint64(iotextypes.ReceiptStatus_Success), @@ -339,7 +339,7 @@ func TestHandleStakeMigrate(t *testing.T) { // native bucket burned csm, err = NewCandidateStateManager(sm) r.NoError(err) - _, err = csm.getBucket(bktIdx) + _, err = csm.NativeBucket(bktIdx) r.ErrorIs(err, state.ErrStateNotExist) // votes reduced for staking indexer not enabled cand := csm.GetByOwner(identityset.Address(candOwnerID)) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index e2a05d5602..95978d3e6b 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -893,8 +893,8 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid return log, nil } -func (p *Protocol) fetchBucket(csm BucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) { - bucket, err := csm.getBucket(index) +func (p *Protocol) fetchBucket(csm NativeBucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) { + bucket, err := csm.NativeBucket(index) if err != nil { fetchErr := &handleError{ err: errors.Wrapf(err, "failed to fetch bucket by index %d", index), diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index 9902192b61..7aa9bf5236 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -112,7 +112,7 @@ func TestProtocol_HandleCreateStake(t *testing.T) { ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) @@ -245,21 +245,21 @@ func TestProtocol_HandleCreateStake(t *testing.T) { require.Equal(test.amount, cLog.Amount.String()) // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidateAddr) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidateAddr) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(stakerAddr) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(stakerAddr) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidateAddr, bucket.Candidate) require.Equal(stakerAddr, bucket.Owner) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candidateAddr) + candidate, _, err := csr.CandidateByAddress(candidateAddr) require.NoError(err) require.LessOrEqual(test.amount, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -644,11 +644,11 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) { } // test candidate - candidate, _, err := csr.getCandidate(act.OwnerAddress()) + candidate, _, err := csr.CandidateByAddress(act.OwnerAddress()) if act.OwnerAddress() == nil { require.Nil(candidate) require.Equal(ErrNilParameters, errors.Cause(err)) - candidate, _, err = csr.getCandidate(test.caller) + candidate, _, err = csr.CandidateByAddress(test.caller) require.NoError(err) require.Equal(test.caller.String(), candidate.Owner.String()) } else { @@ -959,11 +959,11 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test candidate - candidate, _, err := csr.getCandidate(act.OwnerAddress()) + candidate, _, err := csr.CandidateByAddress(act.OwnerAddress()) if act.OwnerAddress() == nil { require.Nil(candidate) require.Equal(ErrNilParameters, errors.Cause(err)) - candidate, _, err = csr.getCandidate(test.caller) + candidate, _, err = csr.CandidateByAddress(test.caller) require.NoError(err) require.Equal(test.caller.String(), candidate.Owner.String()) } else { @@ -1203,21 +1203,21 @@ func TestProtocol_HandleUnstake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket csr = newCandidateStateReader(sm) - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterUnstake, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -1238,7 +1238,7 @@ func TestProtocol_HandleUnstake(t *testing.T) { } // verify bucket unstaked - vb, err := csr.getBucket(0) + vb, err := csr.NativeBucket(0) require.NoError(err) require.True(vb.isUnstaked()) @@ -1294,7 +1294,7 @@ func TestProtocol_HandleUnstake(t *testing.T) { if !v.greenland { // pre-Greenland allows restaking an unstaked bucket, and it is considered staked afterwards - vb, err := csr.getBucket(0) + vb, err := csr.NativeBucket(0) require.NoError(err) require.True(vb.StakeStartTime.Unix() != 0) require.True(vb.UnstakeStartTime.Unix() != 0) @@ -1501,9 +1501,9 @@ func TestProtocol_HandleWithdrawStake(t *testing.T) { require.Equal(test.amount, wLog.Amount.String()) // test bucket index and bucket - _, _, err := csr.candBucketIndices(candidate.Owner) + _, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.Error(err) - _, _, err = csr.voterBucketIndices(candidate.Owner) + _, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.Error(err) // test staker's account @@ -1776,21 +1776,21 @@ func TestProtocol_HandleChangeCandidate(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(identityset.Address(1)) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(identityset.Address(1)) require.NoError(err) require.Equal(2, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(identityset.Address(1)) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(identityset.Address(1)) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(identityset.Address(1).String(), bucket.Candidate.String()) require.Equal(identityset.Address(1).String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candidate.Owner) + candidate, _, err := csr.CandidateByAddress(candidate.Owner) require.NotNil(candidate) require.NoError(err) require.Equal(test.afterChange, candidate.Votes.String()) @@ -2126,21 +2126,21 @@ func TestProtocol_HandleTransferStake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate2.GetIdentifier()) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate2.GetIdentifier()) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(test.to) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(test.to) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate2.GetIdentifier(), bucket.Candidate) require.Equal(test.to.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candi.Owner) + candidate, _, err := csr.CandidateByAddress(candi.Owner) require.NoError(err) require.Equal(test.afterTransfer, candidate.Votes.Uint64()) csm, err := NewCandidateStateManager(sm) @@ -2359,21 +2359,21 @@ func TestProtocol_HandleConsignmentTransfer(t *testing.T) { if test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(cand2.GetIdentifier()) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(cand2.GetIdentifier()) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(test.to) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(test.to) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(cand2.GetIdentifier(), bucket.Candidate) require.Equal(test.to.String(), bucket.Owner.String()) require.Equal(stakeAmount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(cand1.GetIdentifier()) + candidate, _, err := csr.CandidateByAddress(cand1.GetIdentifier()) require.NoError(err) require.LessOrEqual(uint64(0), candidate.Votes.Uint64()) csm, err := NewCandidateStateManager(sm) @@ -2628,21 +2628,21 @@ func TestProtocol_HandleRestake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterRestake, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -2844,15 +2844,15 @@ func TestProtocol_HandleDepositToStake(t *testing.T) { require.Equal(test.amount, dLog.Amount.String()) // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) @@ -2860,7 +2860,7 @@ func TestProtocol_HandleDepositToStake(t *testing.T) { require.Zero(new(big.Int).Mul(amount, big.NewInt(2)).Cmp(bucket.StakedAmount)) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterDeposit, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -2889,7 +2889,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { t.Run("bucket not exist", func(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) - patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches := gomonkey.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return nil, state.ErrStateNotExist }) defer patches.Reset() @@ -2899,7 +2899,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { t.Run("validate owner", func(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) - patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches := gomonkey.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -2915,7 +2915,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { require.NoError(err) patches := gomonkey.NewPatches() defer patches.Reset() - patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -2938,7 +2938,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) patches := gomonkey.NewPatches() - patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -3309,7 +3309,7 @@ func initCreateStake(t *testing.T, sm protocol.StateManager, callerAddr address. ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) _, err = p.Handle(ctx, elp, sm) @@ -3355,7 +3355,7 @@ func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Pro ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) return sm, p, candidate, candidate2 diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 95a1592a7d..a57112df3b 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -117,7 +117,7 @@ type ( func WithContractStakingIndexerV3(indexer ContractStakingIndexer) Option { return func(p *Protocol) { p.contractStakingIndexerV3 = indexer - p.config.TimestampedMigrateContractAddress = indexer.ContractAddress() + p.config.TimestampedMigrateContractAddress = indexer.ContractAddress().String() return } } @@ -184,7 +184,7 @@ func NewProtocol( voteReviser := NewVoteReviser(cfg.Revise) migrateContractAddress := "" if contractStakingIndexerV2 != nil { - migrateContractAddress = contractStakingIndexerV2.ContractAddress() + migrateContractAddress = contractStakingIndexerV2.ContractAddress().String() } p := &Protocol{ addr: addr, @@ -249,23 +249,23 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol c.contractsStake = &contractStakeView{} if p.contractStakingIndexer != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexer.ContractAddress()) } c.contractsStake.v1 = view } if p.contractStakingIndexerV2 != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV2, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV2, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer v2") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexerV2.ContractAddress()) } c.contractsStake.v2 = view } if p.contractStakingIndexerV3 != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV3, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV3, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer v3") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexerV3.ContractAddress()) } c.contractsStake.v3 = view } @@ -425,7 +425,12 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager if err != nil { return err } - if err = v.(*ViewData).contractsStake.CreatePreStates(ctx); err != nil { + if blkCtx.BlockHeight == g.ToBeEnabledBlockHeight { + if err := v.(*viewData).contractsStake.FlushBuckets(sm); err != nil { + return errors.Wrap(err, "failed to write buckets") + } + } + if err = v.(*viewData).contractsStake.CreatePreStates(ctx); err != nil { return err } return nil @@ -444,15 +449,14 @@ func (p *Protocol) PreCommit(ctx context.Context, sm protocol.StateManager) erro if err != nil { return err } - vd := view.(*ViewData) + vd := view.(*viewData) if !vd.IsDirty() { return nil } - clone := vd.candCenter.Clone() - if err := clone.Commit(ctx, sm); err != nil { + if err := vd.Commit(ctx, sm); err != nil { return err } - return clone.WriteToStateDB(sm) + return vd.candCenter.WriteToStateDB(sm) } // Commit commits the last change @@ -461,7 +465,7 @@ func (p *Protocol) Commit(ctx context.Context, sm protocol.StateManager) error { if err != nil { return err } - if !view.(*ViewData).IsDirty() { + if !view.(*viewData).IsDirty() { return nil } @@ -564,7 +568,7 @@ func (p *Protocol) HandleReceipt(ctx context.Context, elp action.Envelope, sm pr if err != nil { return err } - return v.(*ViewData).contractsStake.Handle(ctx, receipt) + return v.(*viewData).contractsStake.Handle(ctx, receipt) } // Validate validates a staking message @@ -618,7 +622,7 @@ func (p *Protocol) isActiveCandidate(ctx context.Context, csr CandidiateStateCom // before endorsement feature, candidates with enough amount must be active return true, nil } - bucket, err := csr.getBucket(cand.SelfStakeBucketIdx) + bucket, err := csr.NativeBucket(cand.SelfStakeBucketIdx) switch { case errors.Cause(err) == state.ErrStateNotExist: // endorse bucket has been withdrawn @@ -873,7 +877,7 @@ func (p *Protocol) contractStakingVotesFromIndexer(ctx context.Context, candidat return votes, nil } -func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *ViewData) (*big.Int, error) { +func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *viewData) (*big.Int, error) { featureCtx := protocol.MustGetFeatureCtx(ctx) votes := big.NewInt(0) views := []ContractStakeView{} @@ -926,47 +930,3 @@ func readCandCenterStateFromStateDB(sr protocol.StateReader) (CandidateList, Can } return name, operator, owner, nil } - -func writeCandCenterStateToStateDB(sm protocol.StateManager, name, op, owners CandidateList) error { - if _, err := sm.PutState(name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { - return err - } - if _, err := sm.PutState(op, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_operatorKey)); err != nil { - return err - } - _, err := sm.PutState(owners, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_ownerKey)) - return err -} - -// isSelfStakeBucket returns true if the bucket is self-stake bucket and not expired -func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csc CandidiateStateCommon, bucket *VoteBucket) (bool, error) { - // bucket index should be settled in one of candidates - selfStake := csc.ContainsSelfStakingBucket(bucket.Index) - if featureCtx.DisableDelegateEndorsement || !selfStake { - return selfStake, nil - } - - // bucket should not be unstaked if it is self-owned - if isSelfOwnedBucket(csc, bucket) { - return !bucket.isUnstaked(), nil - } - // otherwise bucket should be an endorse bucket which is not expired - esm := NewEndorsementStateReader(csc.SR()) - height, err := esm.Height() - if err != nil { - return false, err - } - status, err := esm.Status(featureCtx, bucket.Index, height) - if err != nil { - return false, err - } - return status != EndorseExpired, nil -} - -func isSelfOwnedBucket(csc CandidiateStateCommon, bucket *VoteBucket) bool { - cand := csc.GetByIdentifier(bucket.Candidate) - if cand == nil { - return false - } - return address.Equal(bucket.Owner, cand.Owner) -} diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 1b127bbf40..6a62ebeafe 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -99,7 +99,7 @@ func TestProtocol(t *testing.T) { }, nil, nil, nil) r.NotNil(stk) r.NoError(err) - buckets, _, err := csr.getAllBuckets() + buckets, _, err := csr.NativeBuckets() r.NoError(err) r.Equal(0, len(buckets)) c, _, err := csr.getAllCandidates() @@ -129,7 +129,7 @@ func TestProtocol(t *testing.T) { v, err := stk.Start(ctx, sm) r.NoError(err) r.NoError(sm.WriteView(_protocolID, v)) - _, ok := v.(*ViewData) + _, ok := v.(*viewData) r.True(ok) csm, err := NewCandidateStateManager(sm) @@ -182,14 +182,14 @@ func TestProtocol(t *testing.T) { r.Equal(c1, c2) // load buckets from stateDB and verify - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() r.NoError(err) r.Equal(len(tests), len(buckets)) // delete one bucket r.NoError(csm.delBucket(1)) - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() r.NoError(csm.delBucket(1)) - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() for _, e := range tests { for i := range buckets { if buckets[i].StakedAmount == e.amount { @@ -462,8 +462,9 @@ func TestProtocol_ActiveCandidates(t *testing.T) { sm.EXPECT().Height().DoAndReturn(func() (uint64, error) { return blkHeight, nil }).AnyTimes() - csIndexer.EXPECT().StartView(gomock.Any()).Return(nil, nil) + // csIndexer.EXPECT().StartView(gomock.Any()).Return(nil, nil) csIndexer.EXPECT().Height().Return(uint64(blkHeight), nil).AnyTimes() + csIndexer.EXPECT().LoadStakeView(gomock.Any(), gomock.Any()).Return(nil, nil) v, err := p.Start(ctx, sm) require.NoError(err) diff --git a/action/protocol/staking/read_state.go b/action/protocol/staking/read_state.go index b72c56be55..3fa16f3cce 100644 --- a/action/protocol/staking/read_state.go +++ b/action/protocol/staking/read_state.go @@ -69,7 +69,7 @@ func toIoTeXTypesCandidateV2(csr CandidateStateReader, cand *Candidate, featureC if !c.isSelfStakeBucketSettled() { return false, nil } - vb, err := csr.getBucket(c.SelfStakeBucketIdx) + vb, err := csr.NativeBucket(c.SelfStakeBucketIdx) if err != nil { if errors.Is(err, state.ErrStateNotExist) { return true, nil diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go index b5e55cbb71..1dc1bedcf7 100644 --- a/action/protocol/staking/stakeview_builder.go +++ b/action/protocol/staking/stakeview_builder.go @@ -32,8 +32,8 @@ func NewContractStakeViewBuilder( } } -func (b *contractStakeViewBuilder) Build(ctx context.Context, height uint64) (ContractStakeView, error) { - view, err := b.indexer.StartView(ctx) +func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateReader, height uint64) (ContractStakeView, error) { + view, err := b.indexer.LoadStakeView(ctx, sr) if err != nil { return nil, err } diff --git a/action/protocol/staking/staking_statereader.go b/action/protocol/staking/staking_statereader.go index bd24c0c272..9011c4df14 100644 --- a/action/protocol/staking/staking_statereader.go +++ b/action/protocol/staking/staking_statereader.go @@ -277,7 +277,7 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co height := c.nativeSR.Height() var targetIndexer ContractStakingIndexerWithBucketType for _, indexer := range c.contractIndexers { - if indexer.ContractAddress() == req.GetContractAddress() { + if indexer.ContractAddress().String() == req.GetContractAddress() { if bt, ok := indexer.(ContractStakingIndexerWithBucketType); ok { targetIndexer = bt } diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index 3589f0524a..d85b8a38e4 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -110,7 +110,7 @@ func TestStakingStateReader(t *testing.T) { sf.EXPECT().Height().Return(uint64(1), nil).AnyTimes() candCenter, err := NewCandidateCenter(testCandidates) r.NoError(err) - testNativeData := &ViewData{ + testNativeData := &viewData{ candCenter: candCenter, bucketPool: &BucketPool{ total: &totalAmount{ diff --git a/action/protocol/staking/stakingpb/staking.pb.go b/action/protocol/staking/stakingpb/staking.pb.go index fe4c0587cb..665c795614 100644 --- a/action/protocol/staking/stakingpb/staking.pb.go +++ b/action/protocol/staking/stakingpb/staking.pb.go @@ -8,9 +8,9 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v4.23.3 -// source: action/protocol/staking/stakingpb/staking.proto +// protoc-gen-go v1.34.2 +// protoc v5.29.3 +// source: staking.proto package stakingpb @@ -53,7 +53,7 @@ type Bucket struct { func (x *Bucket) Reset() { *x = Bucket{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] + mi := &file_staking_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -66,7 +66,7 @@ func (x *Bucket) String() string { func (*Bucket) ProtoMessage() {} func (x *Bucket) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] + mi := &file_staking_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -79,7 +79,7 @@ func (x *Bucket) ProtoReflect() protoreflect.Message { // Deprecated: Use Bucket.ProtoReflect.Descriptor instead. func (*Bucket) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{0} + return file_staking_proto_rawDescGZIP(), []int{0} } func (x *Bucket) GetIndex() uint64 { @@ -191,7 +191,7 @@ type BucketIndices struct { func (x *BucketIndices) Reset() { *x = BucketIndices{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] + mi := &file_staking_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -204,7 +204,7 @@ func (x *BucketIndices) String() string { func (*BucketIndices) ProtoMessage() {} func (x *BucketIndices) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] + mi := &file_staking_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -217,7 +217,7 @@ func (x *BucketIndices) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketIndices.ProtoReflect.Descriptor instead. func (*BucketIndices) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{1} + return file_staking_proto_rawDescGZIP(), []int{1} } func (x *BucketIndices) GetIndices() []uint64 { @@ -246,7 +246,7 @@ type Candidate struct { func (x *Candidate) Reset() { *x = Candidate{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] + mi := &file_staking_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -259,7 +259,7 @@ func (x *Candidate) String() string { func (*Candidate) ProtoMessage() {} func (x *Candidate) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] + mi := &file_staking_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -272,7 +272,7 @@ func (x *Candidate) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidate.ProtoReflect.Descriptor instead. func (*Candidate) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{2} + return file_staking_proto_rawDescGZIP(), []int{2} } func (x *Candidate) GetOwnerAddress() string { @@ -349,7 +349,7 @@ type Candidates struct { func (x *Candidates) Reset() { *x = Candidates{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] + mi := &file_staking_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -362,7 +362,7 @@ func (x *Candidates) String() string { func (*Candidates) ProtoMessage() {} func (x *Candidates) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] + mi := &file_staking_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -375,7 +375,7 @@ func (x *Candidates) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidates.ProtoReflect.Descriptor instead. func (*Candidates) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{3} + return file_staking_proto_rawDescGZIP(), []int{3} } func (x *Candidates) GetCandidates() []*Candidate { @@ -397,7 +397,7 @@ type TotalAmount struct { func (x *TotalAmount) Reset() { *x = TotalAmount{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] + mi := &file_staking_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -410,7 +410,7 @@ func (x *TotalAmount) String() string { func (*TotalAmount) ProtoMessage() {} func (x *TotalAmount) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] + mi := &file_staking_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -423,7 +423,7 @@ func (x *TotalAmount) ProtoReflect() protoreflect.Message { // Deprecated: Use TotalAmount.ProtoReflect.Descriptor instead. func (*TotalAmount) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{4} + return file_staking_proto_rawDescGZIP(), []int{4} } func (x *TotalAmount) GetAmount() string { @@ -453,7 +453,7 @@ type BucketType struct { func (x *BucketType) Reset() { *x = BucketType{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] + mi := &file_staking_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +466,7 @@ func (x *BucketType) String() string { func (*BucketType) ProtoMessage() {} func (x *BucketType) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] + mi := &file_staking_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +479,7 @@ func (x *BucketType) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketType.ProtoReflect.Descriptor instead. func (*BucketType) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{5} + return file_staking_proto_rawDescGZIP(), []int{5} } func (x *BucketType) GetAmount() string { @@ -514,7 +514,7 @@ type Endorsement struct { func (x *Endorsement) Reset() { *x = Endorsement{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] + mi := &file_staking_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -527,7 +527,7 @@ func (x *Endorsement) String() string { func (*Endorsement) ProtoMessage() {} func (x *Endorsement) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] + mi := &file_staking_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -540,7 +540,7 @@ func (x *Endorsement) ProtoReflect() protoreflect.Message { // Deprecated: Use Endorsement.ProtoReflect.Descriptor instead. func (*Endorsement) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{6} + return file_staking_proto_rawDescGZIP(), []int{6} } func (x *Endorsement) GetExpireHeight() uint64 { @@ -550,118 +550,285 @@ func (x *Endorsement) GetExpireHeight() uint64 { return 0 } -var File_action_protocol_staking_stakingpb_staking_proto protoreflect.FileDescriptor - -var file_action_protocol_staking_stakingpb_staking_proto_rawDesc = []byte{ - 0x0a, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, - 0x67, 0x70, 0x62, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, - 0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, - 0x0a, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, - 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, - 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x19, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, - 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, - 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, - 0x63, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, - 0x2e, 0x0a, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, - 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, - 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, - 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, - 0x62, 0x2e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, - 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, - 0x72, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x46, 0x5a, 0x44, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, - 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +type SystemStakingContract struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NumOfBuckets uint64 `protobuf:"varint,1,opt,name=numOfBuckets,proto3" json:"numOfBuckets,omitempty"` +} + +func (x *SystemStakingContract) Reset() { + *x = SystemStakingContract{} + if protoimpl.UnsafeEnabled { + mi := &file_staking_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemStakingContract) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemStakingContract) ProtoMessage() {} + +func (x *SystemStakingContract) ProtoReflect() protoreflect.Message { + mi := &file_staking_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemStakingContract.ProtoReflect.Descriptor instead. +func (*SystemStakingContract) Descriptor() ([]byte, []int) { + return file_staking_proto_rawDescGZIP(), []int{7} +} + +func (x *SystemStakingContract) GetNumOfBuckets() uint64 { + if x != nil { + return x.NumOfBuckets + } + return 0 +} + +type SystemStakingBucket struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Owner []byte `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + Candidate []byte `protobuf:"bytes,2,opt,name=candidate,proto3" json:"candidate,omitempty"` + Amount []byte `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Duration uint64 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` + CreatedAt uint64 `protobuf:"varint,5,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + UnlockedAt uint64 `protobuf:"varint,6,opt,name=unlockedAt,proto3" json:"unlockedAt,omitempty"` + UnstakedAt uint64 `protobuf:"varint,7,opt,name=unstakedAt,proto3" json:"unstakedAt,omitempty"` + Muted bool `protobuf:"varint,8,opt,name=muted,proto3" json:"muted,omitempty"` +} + +func (x *SystemStakingBucket) Reset() { + *x = SystemStakingBucket{} + if protoimpl.UnsafeEnabled { + mi := &file_staking_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemStakingBucket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemStakingBucket) ProtoMessage() {} + +func (x *SystemStakingBucket) ProtoReflect() protoreflect.Message { + mi := &file_staking_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemStakingBucket.ProtoReflect.Descriptor instead. +func (*SystemStakingBucket) Descriptor() ([]byte, []int) { + return file_staking_proto_rawDescGZIP(), []int{8} +} + +func (x *SystemStakingBucket) GetOwner() []byte { + if x != nil { + return x.Owner + } + return nil +} + +func (x *SystemStakingBucket) GetCandidate() []byte { + if x != nil { + return x.Candidate + } + return nil +} + +func (x *SystemStakingBucket) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +func (x *SystemStakingBucket) GetDuration() uint64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *SystemStakingBucket) GetCreatedAt() uint64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetUnlockedAt() uint64 { + if x != nil { + return x.UnlockedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetUnstakedAt() uint64 { + if x != nil { + return x.UnstakedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetMuted() bool { + if x != nil { + return x.Muted + } + return false +} + +var File_staking_proto protoreflect.FileDescriptor + +var file_staking_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, 0x0a, 0x06, + 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x10, + 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x73, + 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x17, + 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x75, + 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, + 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, + 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x2e, 0x0a, + 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, + 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x2e, + 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x73, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3b, 0x0a, 0x15, 0x53, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, 0x75, 0x63, 0x6b, 0x65, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, + 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x22, 0xf1, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, + 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, + 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce sync.Once - file_action_protocol_staking_stakingpb_staking_proto_rawDescData = file_action_protocol_staking_stakingpb_staking_proto_rawDesc + file_staking_proto_rawDescOnce sync.Once + file_staking_proto_rawDescData = file_staking_proto_rawDesc ) -func file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP() []byte { - file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce.Do(func() { - file_action_protocol_staking_stakingpb_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_action_protocol_staking_stakingpb_staking_proto_rawDescData) +func file_staking_proto_rawDescGZIP() []byte { + file_staking_proto_rawDescOnce.Do(func() { + file_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_staking_proto_rawDescData) }) - return file_action_protocol_staking_stakingpb_staking_proto_rawDescData + return file_staking_proto_rawDescData } -var file_action_protocol_staking_stakingpb_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 7) -var file_action_protocol_staking_stakingpb_staking_proto_goTypes = []interface{}{ +var file_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_staking_proto_goTypes = []any{ (*Bucket)(nil), // 0: stakingpb.Bucket (*BucketIndices)(nil), // 1: stakingpb.BucketIndices (*Candidate)(nil), // 2: stakingpb.Candidate @@ -669,12 +836,14 @@ var file_action_protocol_staking_stakingpb_staking_proto_goTypes = []interface{} (*TotalAmount)(nil), // 4: stakingpb.TotalAmount (*BucketType)(nil), // 5: stakingpb.BucketType (*Endorsement)(nil), // 6: stakingpb.Endorsement - (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp -} -var file_action_protocol_staking_stakingpb_staking_proto_depIdxs = []int32{ - 7, // 0: stakingpb.Bucket.createTime:type_name -> google.protobuf.Timestamp - 7, // 1: stakingpb.Bucket.stakeStartTime:type_name -> google.protobuf.Timestamp - 7, // 2: stakingpb.Bucket.unstakeStartTime:type_name -> google.protobuf.Timestamp + (*SystemStakingContract)(nil), // 7: stakingpb.SystemStakingContract + (*SystemStakingBucket)(nil), // 8: stakingpb.SystemStakingBucket + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp +} +var file_staking_proto_depIdxs = []int32{ + 9, // 0: stakingpb.Bucket.createTime:type_name -> google.protobuf.Timestamp + 9, // 1: stakingpb.Bucket.stakeStartTime:type_name -> google.protobuf.Timestamp + 9, // 2: stakingpb.Bucket.unstakeStartTime:type_name -> google.protobuf.Timestamp 2, // 3: stakingpb.Candidates.candidates:type_name -> stakingpb.Candidate 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type @@ -683,13 +852,13 @@ var file_action_protocol_staking_stakingpb_staking_proto_depIdxs = []int32{ 0, // [0:4] is the sub-list for field type_name } -func init() { file_action_protocol_staking_stakingpb_staking_proto_init() } -func file_action_protocol_staking_stakingpb_staking_proto_init() { - if File_action_protocol_staking_stakingpb_staking_proto != nil { +func init() { file_staking_proto_init() } +func file_staking_proto_init() { + if File_staking_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Bucket); i { case 0: return &v.state @@ -701,7 +870,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*BucketIndices); i { case 0: return &v.state @@ -713,7 +882,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*Candidate); i { case 0: return &v.state @@ -725,7 +894,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Candidates); i { case 0: return &v.state @@ -737,7 +906,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*TotalAmount); i { case 0: return &v.state @@ -749,7 +918,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*BucketType); i { case 0: return &v.state @@ -761,7 +930,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*Endorsement); i { case 0: return &v.state @@ -773,23 +942,47 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } + file_staking_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*SystemStakingContract); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_staking_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*SystemStakingBucket); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_action_protocol_staking_stakingpb_staking_proto_rawDesc, + RawDescriptor: file_staking_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_action_protocol_staking_stakingpb_staking_proto_goTypes, - DependencyIndexes: file_action_protocol_staking_stakingpb_staking_proto_depIdxs, - MessageInfos: file_action_protocol_staking_stakingpb_staking_proto_msgTypes, + GoTypes: file_staking_proto_goTypes, + DependencyIndexes: file_staking_proto_depIdxs, + MessageInfos: file_staking_proto_msgTypes, }.Build() - File_action_protocol_staking_stakingpb_staking_proto = out.File - file_action_protocol_staking_stakingpb_staking_proto_rawDesc = nil - file_action_protocol_staking_stakingpb_staking_proto_goTypes = nil - file_action_protocol_staking_stakingpb_staking_proto_depIdxs = nil + File_staking_proto = out.File + file_staking_proto_rawDesc = nil + file_staking_proto_goTypes = nil + file_staking_proto_depIdxs = nil } diff --git a/action/protocol/staking/stakingpb/staking.proto b/action/protocol/staking/stakingpb/staking.proto index 3a2e2c8cb1..236279f1c9 100644 --- a/action/protocol/staking/stakingpb/staking.proto +++ b/action/protocol/staking/stakingpb/staking.proto @@ -62,3 +62,18 @@ message BucketType { message Endorsement { uint64 expireHeight = 1; } + +message SystemStakingContract { + uint64 numOfBuckets = 1; +} + +message SystemStakingBucket { + bytes owner = 1; + bytes candidate = 2; + bytes amount = 3; + uint64 duration = 4; + uint64 createdAt = 5; + uint64 unlockedAt = 6; + uint64 unstakedAt = 7; + bool muted = 8; +} diff --git a/action/protocol/staking/util.go b/action/protocol/staking/util.go new file mode 100644 index 0000000000..7280557b83 --- /dev/null +++ b/action/protocol/staking/util.go @@ -0,0 +1,39 @@ +package staking + +import ( + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" +) + +// isSelfStakeBucket returns true if the bucket is self-stake bucket and not expired +func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csc CandidiateStateCommon, bucket *VoteBucket) (bool, error) { + // bucket index should be settled in one of candidates + selfStake := csc.ContainsSelfStakingBucket(bucket.Index) + if featureCtx.DisableDelegateEndorsement || !selfStake { + return selfStake, nil + } + + // bucket should not be unstaked if it is self-owned + if isSelfOwnedBucket(csc, bucket) { + return !bucket.isUnstaked(), nil + } + // otherwise bucket should be an endorse bucket which is not expired + esm := NewEndorsementStateReader(csc.SR()) + height, err := esm.Height() + if err != nil { + return false, err + } + status, err := esm.Status(featureCtx, bucket.Index, height) + if err != nil { + return false, err + } + return status != EndorseExpired, nil +} + +func isSelfOwnedBucket(csc CandidiateStateCommon, bucket *VoteBucket) bool { + cand := csc.GetByIdentifier(bucket.Candidate) + if cand == nil { + return false + } + return address.Equal(bucket.Owner, cand.Owner) +} diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index f2b7edebee..108b33bf6d 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -23,17 +23,19 @@ type ( // Fork forks the contract stake view, commit will not affect the original view Fork() ContractStakeView // Commit commits the contract stake view - Commit() + Commit(context.Context, protocol.StateManager) error // CreatePreStates creates pre states for the contract stake view CreatePreStates(ctx context.Context) error // Handle handles the receipt for the contract stake view Handle(ctx context.Context, receipt *action.Receipt) error + // WriteBuckets writes the buckets to the state manager + WriteBuckets(protocol.StateManager) error // BucketsByCandidate returns the buckets by candidate address BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error } - // ViewData is the data that need to be stored in protocol's view - ViewData struct { + // viewData is the data that need to be stored in protocol's view + viewData struct { candCenter *CandidateCenter bucketPool *BucketPool snapshots []Snapshot @@ -53,8 +55,8 @@ type ( } ) -func (v *ViewData) Fork() protocol.View { - fork := &ViewData{} +func (v *viewData) Fork() protocol.View { + fork := &viewData{} fork.candCenter = v.candCenter.Clone() fork.bucketPool = v.bucketPool.Clone() fork.snapshots = make([]Snapshot, len(v.snapshots)) @@ -71,26 +73,28 @@ func (v *ViewData) Fork() protocol.View { return fork } -func (v *ViewData) Commit(ctx context.Context, sr protocol.StateReader) error { - if err := v.candCenter.Commit(ctx, sr); err != nil { +func (v *viewData) Commit(ctx context.Context, sm protocol.StateManager) error { + if err := v.candCenter.Commit(ctx, sm); err != nil { return err } - if err := v.bucketPool.Commit(sr); err != nil { + if err := v.bucketPool.Commit(); err != nil { return err } if v.contractsStake != nil { - v.contractsStake.Commit() + if err := v.contractsStake.Commit(ctx, sm); err != nil { + return err + } } v.snapshots = []Snapshot{} return nil } -func (v *ViewData) IsDirty() bool { +func (v *viewData) IsDirty() bool { return v.candCenter.IsDirty() || v.bucketPool.IsDirty() } -func (v *ViewData) Snapshot() int { +func (v *viewData) Snapshot() int { snapshot := len(v.snapshots) wrapped := v.contractsStake.Wrap() v.snapshots = append(v.snapshots, Snapshot{ @@ -104,7 +108,7 @@ func (v *ViewData) Snapshot() int { return snapshot } -func (v *ViewData) Revert(snapshot int) error { +func (v *viewData) Revert(snapshot int) error { if snapshot < 0 || snapshot >= len(v.snapshots) { return errors.Errorf("invalid snapshot index %d", snapshot) } @@ -122,6 +126,25 @@ func (v *ViewData) Revert(snapshot int) error { return nil } +func (csv *contractStakeView) FlushBuckets(sm protocol.StateManager) error { + if csv.v1 != nil { + if err := csv.v1.WriteBuckets(sm); err != nil { + return err + } + } + if csv.v2 != nil { + if err := csv.v2.WriteBuckets(sm); err != nil { + return err + } + } + if csv.v3 != nil { + if err := csv.v3.WriteBuckets(sm); err != nil { + return err + } + } + return nil +} + func (csv *contractStakeView) Wrap() *contractStakeView { if csv == nil { return nil @@ -175,16 +198,27 @@ func (csv *contractStakeView) CreatePreStates(ctx context.Context) error { return nil } -func (csv *contractStakeView) Commit() { +func (csv *contractStakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok || featureCtx.LoadContractStakingFromIndexer { + sm = nil + } if csv.v1 != nil { - csv.v1.Commit() + if err := csv.v1.Commit(ctx, sm); err != nil { + return err + } } if csv.v2 != nil { - csv.v2.Commit() + if err := csv.v2.Commit(ctx, sm); err != nil { + return err + } } if csv.v3 != nil { - csv.v3.Commit() + if err := csv.v3.Commit(ctx, sm); err != nil { + return err + } } + return nil } func (csv *contractStakeView) Handle(ctx context.Context, receipt *action.Receipt) error { diff --git a/action/protocol/staking/viewdata_test.go b/action/protocol/staking/viewdata_test.go index 447769d14f..db506df05f 100644 --- a/action/protocol/staking/viewdata_test.go +++ b/action/protocol/staking/viewdata_test.go @@ -13,32 +13,32 @@ import ( ) func TestViewData_Fork(t *testing.T) { - viewData, _ := prepareViewData(t) - fork, ok := viewData.Fork().(*ViewData) + vd, _ := prepareViewData(t) + fork, ok := vd.Fork().(*viewData) require.True(t, ok) require.NotNil(t, fork) - require.Equal(t, viewData.candCenter.size, fork.candCenter.size) - require.Equal(t, viewData.candCenter.base, fork.candCenter.base) - require.Equal(t, viewData.candCenter.change, fork.candCenter.change) - require.NotSame(t, viewData.bucketPool, fork.bucketPool) - require.Equal(t, viewData.snapshots, fork.snapshots) + require.Equal(t, vd.candCenter.size, fork.candCenter.size) + require.Equal(t, vd.candCenter.base, fork.candCenter.base) + require.Equal(t, vd.candCenter.change, fork.candCenter.change) + require.NotSame(t, vd.bucketPool, fork.bucketPool) + require.Equal(t, vd.snapshots, fork.snapshots) - sr := mock_chainmanager.NewMockStateReader(gomock.NewController(t)) - sr.EXPECT().Height().Return(uint64(100), nil).Times(1) - require.NoError(t, viewData.Commit(context.Background(), sr)) + sm := mock_chainmanager.NewMockStateManager(gomock.NewController(t)) + sm.EXPECT().Height().Return(uint64(100), nil).Times(1) + require.NoError(t, vd.Commit(context.Background(), sm)) - fork, ok = viewData.Fork().(*ViewData) + fork, ok = vd.Fork().(*viewData) require.True(t, ok) require.NotNil(t, fork) - require.Equal(t, viewData.candCenter.size, fork.candCenter.size) - require.Equal(t, viewData.candCenter.base, fork.candCenter.base) - require.Equal(t, viewData.candCenter.change, fork.candCenter.change) - require.Equal(t, viewData.bucketPool, fork.bucketPool) - require.Equal(t, viewData.snapshots, fork.snapshots) + require.Equal(t, vd.candCenter.size, fork.candCenter.size) + require.Equal(t, vd.candCenter.base, fork.candCenter.base) + require.Equal(t, vd.candCenter.change, fork.candCenter.change) + require.Equal(t, vd.bucketPool, fork.bucketPool) + require.Equal(t, vd.snapshots, fork.snapshots) } -func prepareViewData(t *testing.T) (*ViewData, int) { +func prepareViewData(t *testing.T) (*viewData, int) { owner := identityset.Address(0) cand := &Candidate{ Owner: owner, @@ -61,7 +61,7 @@ func prepareViewData(t *testing.T) (*ViewData, int) { count: 1, }, } - viewData := &ViewData{ + viewData := &viewData{ candCenter: candCenter, bucketPool: bucketPool, snapshots: []Snapshot{}, @@ -72,9 +72,9 @@ func prepareViewData(t *testing.T) (*ViewData, int) { func TestViewData_Commit(t *testing.T) { viewData, _ := prepareViewData(t) require.True(t, viewData.IsDirty()) - mockStateReader := mock_chainmanager.NewMockStateReader(gomock.NewController(t)) - mockStateReader.EXPECT().Height().Return(uint64(100), nil).Times(1) - require.NoError(t, viewData.Commit(context.Background(), mockStateReader)) + mockStateManager := mock_chainmanager.NewMockStateManager(gomock.NewController(t)) + mockStateManager.EXPECT().Height().Return(uint64(100), nil).Times(1) + require.NoError(t, viewData.Commit(context.Background(), mockStateManager)) require.False(t, viewData.IsDirty()) require.Empty(t, viewData.candCenter.change.dirty) require.False(t, viewData.bucketPool.dirty) diff --git a/action/protocol/staking/vote_bucket_test.go b/action/protocol/staking/vote_bucket_test.go index 3c0738b44c..52b2a24d42 100644 --- a/action/protocol/staking/vote_bucket_test.go +++ b/action/protocol/staking/vote_bucket_test.go @@ -66,41 +66,41 @@ func TestGetPutStaking(t *testing.T) { // put buckets and get for _, e := range tests { addr, _ := address.FromBytes(e.name[:]) - _, err := csr.getBucket(e.index) + _, err := csr.NativeBucket(e.index) require.Equal(state.ErrStateNotExist, errors.Cause(err)) vb := NewVoteBucket(addr, identityset.Address(1), big.NewInt(2100000000), 21*uint32(e.index+1), time.Now(), true) - count, err := csr.getTotalBucketCount() + count, err := csr.NumOfNativeBucket() require.NoError(err) require.Equal(e.index, count) count, err = csm.putBucket(vb) require.NoError(err) require.Equal(e.index, count) - count, err = csr.getTotalBucketCount() + count, err = csr.NumOfNativeBucket() require.NoError(err) require.Equal(e.index+1, count) - vb1, err := csr.getBucket(e.index) + vb1, err := csr.NativeBucket(e.index) require.NoError(err) require.Equal(e.index, vb1.Index) require.Equal(vb, vb1) } - vb, err := csr.getBucket(2) + vb, err := csr.NativeBucket(2) require.NoError(err) vb.AutoStake = false vb.StakedAmount.Sub(vb.StakedAmount, big.NewInt(100)) vb.UnstakeStartTime = time.Now().UTC() require.True(vb.isUnstaked()) require.NoError(csm.updateBucket(2, vb)) - vb1, err := csr.getBucket(2) + vb1, err := csr.NativeBucket(2) require.NoError(err) require.Equal(vb, vb1) // delete buckets and get for _, e := range tests { require.NoError(csm.delBucket(e.index)) - _, err := csr.getBucket(e.index) + _, err := csr.NativeBucket(e.index) require.Equal(state.ErrStateNotExist, errors.Cause(err)) } } diff --git a/action/protocol/staking/vote_reviser.go b/action/protocol/staking/vote_reviser.go index ebfd4acc98..844d0c324a 100644 --- a/action/protocol/staking/vote_reviser.go +++ b/action/protocol/staking/vote_reviser.go @@ -130,7 +130,7 @@ func (vr *VoteReviser) correctCandSelfStake(ctx protocol.FeatureCtx, csm Candida cand.SelfStake = big.NewInt(0) continue } - sb, err := csm.getBucket(cand.SelfStakeBucketIdx) + sb, err := csm.NativeBucket(cand.SelfStakeBucketIdx) switch errors.Cause(err) { case state.ErrStateNotExist: // bucket has been withdrawn @@ -194,7 +194,7 @@ func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager, cands Cand candm[cand.GetIdentifier().String()].Votes = new(big.Int) candm[cand.GetIdentifier().String()].SelfStake = new(big.Int) } - buckets, _, err := csr.getAllBuckets() + buckets, _, err := csr.NativeBuckets() switch { case errors.Cause(err) == state.ErrStateNotExist: case err != nil: diff --git a/action/protocol/staking/vote_reviser_test.go b/action/protocol/staking/vote_reviser_test.go index 72db8ef6f2..2dc1307d52 100644 --- a/action/protocol/staking/vote_reviser_test.go +++ b/action/protocol/staking/vote_reviser_test.go @@ -152,7 +152,7 @@ func TestVoteReviser(t *testing.T) { v, err := stk.Start(ctx, sm) r.NoError(err) r.NoError(sm.WriteView(_protocolID, v)) - _, ok := v.(*ViewData) + _, ok := v.(*viewData) r.True(ok) csm, err = NewCandidateStateManager(sm) @@ -207,10 +207,10 @@ func TestVoteReviser(t *testing.T) { } for _, v := range tests { if address.Equal(v.cand, c.Owner) && v.index != c.SelfStakeBucketIdx { - bucket, err := csr.getBucket(v.index) + bucket, err := csr.NativeBucket(v.index) r.NoError(err) total := CalculateVoteWeight(cv, bucket, false) - bucket, err = csr.getBucket(c.SelfStakeBucketIdx) + bucket, err = csr.NativeBucket(c.SelfStakeBucketIdx) r.NoError(err) total.Add(total, CalculateVoteWeight(cv, bucket, true)) r.Equal(0, total.Cmp(c.Votes)) diff --git a/blockindex/contractstaking/bucket_info.go b/blockindex/contractstaking/bucket_info.go index 0359050d5c..a357f79895 100644 --- a/blockindex/contractstaking/bucket_info.go +++ b/blockindex/contractstaking/bucket_info.go @@ -10,7 +10,6 @@ import ( "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-core/v2/blockindex/contractstaking/contractstakingpb" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) type ( @@ -26,8 +25,8 @@ type ( ) // Serialize serializes the bucket info -func (bi *bucketInfo) Serialize() []byte { - return byteutil.Must(proto.Marshal(bi.toProto())) +func (bi *bucketInfo) Serialize() ([]byte, error) { + return proto.Marshal(bi.toProto()) } // Deserialize deserializes the bucket info diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index 39aae467d6..68ca596fda 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -6,12 +6,16 @@ package contractstaking import ( + "context" + "log" "math/big" "sync" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -24,6 +28,7 @@ type ( MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) BucketType(id uint64) (*BucketType, bool) BucketTypeCount() int + Buckets() ([]uint64, []*BucketType, []*bucketInfo) BucketsByCandidate(candidate address.Address) ([]uint64, []*BucketType, []*bucketInfo) TotalBucketCount() uint64 IsDirty() bool @@ -31,7 +36,7 @@ type ( PutBucketType(id uint64, bt *BucketType) PutBucketInfo(id uint64, bi *bucketInfo) DeleteBucketInfo(id uint64) - Commit() stakingCache + Commit(context.Context, address.Address, protocol.StateManager) (stakingCache, error) Clone() stakingCache } contractStakingCache struct { @@ -42,6 +47,9 @@ type ( totalBucketCount uint64 // total number of buckets including burned buckets height uint64 // current block height, it's put in cache for consistency on merge mutex sync.RWMutex // a RW mutex for the cache to protect concurrent access + + deltaBucketTypes map[uint64]*BucketType + deltaBuckets map[uint64]*contractstaking.Bucket } ) @@ -58,6 +66,8 @@ func newContractStakingCache() *contractStakingCache { bucketTypeMap: make(map[uint64]*BucketType), propertyBucketTypeMap: make(map[int64]map[uint64]uint64), candidateBucketMap: make(map[string]map[uint64]bool), + deltaBucketTypes: make(map[uint64]*BucketType), + deltaBuckets: make(map[uint64]*contractstaking.Bucket), } } @@ -170,6 +180,7 @@ func (s *contractStakingCache) PutBucketType(id uint64, bt *BucketType) { defer s.mutex.Unlock() s.putBucketType(id, bt) + s.deltaBucketTypes[id] = bt } func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) { @@ -177,6 +188,18 @@ func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) { defer s.mutex.Unlock() s.putBucketInfo(id, bi) + bt := s.mustGetBucketType(bi.TypeIndex) + s.deltaBuckets[id] = &contractstaking.Bucket{ + Candidate: bi.Delegate, + Owner: bi.Owner, + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: bi.CreatedAt, + UnstakedAt: bi.UnstakedAt, + UnlockedAt: bi.UnlockedAt, + Muted: false, + IsTimestampBased: false, + } } func (s *contractStakingCache) DeleteBucketInfo(id uint64) { @@ -184,6 +207,7 @@ func (s *contractStakingCache) DeleteBucketInfo(id uint64) { defer s.mutex.Unlock() s.deleteBucketInfo(id) + s.deltaBuckets[id] = nil } func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { @@ -271,6 +295,14 @@ func (s *contractStakingCache) Clone() stakingCache { c.propertyBucketTypeMap[k][k1] = v1 } } + c.deltaBucketTypes = make(map[uint64]*BucketType, len(s.deltaBucketTypes)) + for k, v := range s.deltaBucketTypes { + c.deltaBucketTypes[k] = v.Clone() + } + c.deltaBuckets = make(map[uint64]*contractstaking.Bucket, len(s.deltaBuckets)) + for k, v := range s.deltaBuckets { + c.deltaBuckets[k] = v.Clone() + } return c } @@ -294,7 +326,7 @@ func (s *contractStakingCache) getBucketType(id uint64) (*BucketType, bool) { func (s *contractStakingCache) mustGetBucketType(id uint64) *BucketType { bt, ok := s.getBucketType(id) if !ok { - panic("bucket type not found") + log.Panicf("bucket type not found: %d", id) } return bt } @@ -401,8 +433,45 @@ func (s *contractStakingCache) IsDirty() bool { return false } -func (s *contractStakingCache) Commit() stakingCache { - s.mutex.RLock() - defer s.mutex.RUnlock() - return s +func (s *contractStakingCache) Commit(ctx context.Context, ca address.Address, sm protocol.StateManager) (stakingCache, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + if sm == nil { + s.deltaBucketTypes = make(map[uint64]*BucketType) + s.deltaBuckets = make(map[uint64]*contractstaking.Bucket) + return s, nil + } + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok { + return s, nil + } + if featureCtx.LoadContractStakingFromIndexer { + return s, nil + } + if len(s.deltaBucketTypes) == 0 && len(s.deltaBuckets) == 0 { + return s, nil + } + cssm := contractstaking.NewContractStakingStateManager(sm) + for id, bt := range s.deltaBucketTypes { + if err := cssm.UpsertBucketType(ca, id, bt); err != nil { + return nil, errors.Wrapf(err, "failed to upsert bucket type %d", id) + } + } + for id, bucket := range s.deltaBuckets { + if bucket == nil { + if err := cssm.DeleteBucket(ca, id); err != nil { + return nil, errors.Wrapf(err, "failed to delete bucket %d", id) + } + } else { + if err := cssm.UpsertBucket(ca, id, bucket); err != nil { + return nil, errors.Wrapf(err, "failed to upsert bucket %d", id) + } + } + } + if err := cssm.UpdateNumOfBuckets(ca, s.totalBucketCount); err != nil { + return nil, errors.Wrapf(err, "failed to update total bucket count %d", s.totalBucketCount) + } + s.deltaBucketTypes = make(map[uint64]*BucketType) + s.deltaBuckets = make(map[uint64]*contractstaking.Bucket) + return s, nil } diff --git a/blockindex/contractstaking/cache_test.go b/blockindex/contractstaking/cache_test.go index 415e532b7c..f96d609908 100644 --- a/blockindex/contractstaking/cache_test.go +++ b/blockindex/contractstaking/cache_test.go @@ -514,9 +514,13 @@ func TestContractStakingCache_LoadFromDB(t *testing.T) { // load from db with bucket bucketInfo := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} - kvstore.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(1), bucketInfo.Serialize()) + bidata, err := bucketInfo.Serialize() + require.NoError(err) + kvstore.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(1), bidata) bucketType := &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1} - kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), bucketType.Serialize()) + btdata, err := bucketType.Serialize() + require.NoError(err) + kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), btdata) err = cache.LoadFromDB(kvstore) require.NoError(err) require.Equal(uint64(10), cache.TotalBucketCount()) @@ -560,6 +564,7 @@ func checkBucket(r *require.Assertions, id uint64, bt *BucketType, bucket *bucke func TestContractStakingCache_MustGetBucketInfo(t *testing.T) { // build test condition to add a bucketInfo cache := newContractStakingCache() + cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) tryCatchMustGetBucketInfo := func(i uint64) (v *bucketInfo, err error) { @@ -606,12 +611,14 @@ func TestContractStakingCache_MustGetBucketType(t *testing.T) { v, err = tryCatchMustGetBucketType(2) r.Nil(v) r.Error(err) - r.Equal(err.Error(), "bucket type not found") + r.Equal(err.Error(), "bucket type not found: 2") } func TestContractStakingCache_DeleteBucketInfo(t *testing.T) { // build test condition to add a bucketInfo cache := newContractStakingCache() + cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) + cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 1}) bi1 := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(1)} bi2 := &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} cache.PutBucketInfo(1, bi1) diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index 171e362881..8f2ce90972 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -50,12 +50,20 @@ func newContractStakingDirty(clean stakingCache) *contractStakingDirty { } func (dirty *contractStakingDirty) addBucketInfo(id uint64, bi *bucketInfo) { - dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), bi.Serialize(), "failed to put bucket info") + data, err := bi.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket info")) + } + dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket info") dirty.cache.PutBucketInfo(id, bi) } func (dirty *contractStakingDirty) updateBucketInfo(id uint64, bi *bucketInfo) { - dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), bi.Serialize(), "failed to put bucket info") + data, err := bi.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket info")) + } + dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket info") dirty.cache.PutBucketInfo(id, bi) } @@ -96,7 +104,11 @@ func (dirty *contractStakingDirty) finalizeBatch() batch.KVStoreBatch { } func (dirty *contractStakingDirty) addBucketType(id uint64, bt *BucketType) { - dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), bt.Serialize(), "failed to put bucket type") + data, err := bt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket type")) + } + dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket type") dirty.cache.PutBucketType(id, bt) } @@ -109,6 +121,10 @@ func (dirty *contractStakingDirty) getBucketTypeCount() uint64 { } func (dirty *contractStakingDirty) updateBucketType(id uint64, bt *BucketType) { - dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), bt.Serialize(), "failed to put bucket type") + data, err := bt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket type")) + } + dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket type") dirty.cache.PutBucketType(id, bt) } diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index 4151263f0f..b6070a28d6 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -168,7 +168,9 @@ func TestContractStakingDirty_finalize(t *testing.T) { require.EqualValues(_StakingBucketTypeNS, info.Namespace()) require.EqualValues(batch.Put, info.WriteType()) require.EqualValues(byteutil.Uint64ToBytesBigEndian(1), info.Key()) - require.EqualValues(bt.Serialize(), info.Value()) + btdata, err := bt.Serialize() + require.NoError(err) + require.EqualValues(btdata, info.Value()) require.Equal(1, cache.BucketTypeCount()) // add bucket info @@ -181,7 +183,9 @@ func TestContractStakingDirty_finalize(t *testing.T) { require.EqualValues(_StakingBucketInfoNS, info.Namespace()) require.EqualValues(batch.Put, info.WriteType()) require.EqualValues(byteutil.Uint64ToBytesBigEndian(1), info.Key()) - require.EqualValues(bi.Serialize(), info.Value()) + bidata, err := bi.Serialize() + require.NoError(err) + require.EqualValues(bidata, info.Value()) totalCnt = cache.TotalBucketCount() require.EqualValues(1, totalCnt) } @@ -219,6 +223,7 @@ func TestContractStakingDirty_noSideEffectOnClean(t *testing.T) { require.False(ok) require.Nil(bi) + clean.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) // update bucket info existed in clean cache clean.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) // update bucket info in dirty cache diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go index 16cee07398..c9b4aae092 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/event_handler.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/db/batch" @@ -373,6 +374,23 @@ func newContractStakingEventHandler(cache stakingCache) *contractStakingEventHan } } +func (eh *contractStakingEventHandler) HandleReceipts(ctx context.Context, height uint64, receipts []*action.Receipt, contractAddr string) error { + for _, receipt := range receipts { + if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { + continue + } + for _, log := range receipt.Logs() { + if log.Address != contractAddr { + continue + } + if err := eh.HandleEvent(ctx, height, log); err != nil { + return err + } + } + } + return nil +} + func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, height uint64, log *action.Log) error { // get event abi abiEvent, err := _stakingInterface.EventByID(common.Hash(log.Topics[0])) diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 8bb29e76a2..d9adc6dfc8 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -13,12 +13,11 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" - "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" @@ -35,11 +34,12 @@ type ( // 1. handle contract staking contract events when new block comes to generate index data // 2. provide query interface for contract staking index data Indexer struct { - kvstore db.KVStore // persistent storage, used to initialize index cache at startup - cache *contractStakingCache // in-memory index for clean data, used to query index data - config Config // indexer config - height uint64 - mu sync.RWMutex + kvstore db.KVStore // persistent storage, used to initialize index cache at startup + cache *contractStakingCache // in-memory index for clean data, used to query index data + config Config // indexer config + height uint64 + mu sync.RWMutex + contractAddr address.Address lifecycle.Readiness } @@ -62,16 +62,18 @@ func NewContractStakingIndexer(kvStore db.KVStore, config Config) (*Indexer, err if kvStore == nil { return nil, errors.New("kv store is nil") } - if _, err := address.FromString(config.ContractAddress); err != nil { + contractAddr, err := address.FromString(config.ContractAddress) + if err != nil { return nil, errors.Wrapf(err, "invalid contract address %s", config.ContractAddress) } if config.CalculateVoteWeight == nil { return nil, errors.New("calculate vote weight function is nil") } return &Indexer{ - kvstore: kvStore, - cache: newContractStakingCache(), - config: config, + kvstore: kvStore, + cache: newContractStakingCache(), + config: config, + contractAddr: contractAddr, }, nil } @@ -83,17 +85,73 @@ func (s *Indexer) Start(ctx context.Context) error { return s.start(ctx) } -// StartView starts the indexer view -func (s *Indexer) StartView(ctx context.Context) (staking.ContractStakeView, error) { +// LoadStakeView loads the contract stake view +func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { if !s.IsReady() { if err := s.start(ctx); err != nil { return nil, err } } + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok || featureCtx.LoadContractStakingFromIndexer { + return &stakeView{ + contractAddr: s.contractAddr, + config: s.config, + cache: s.cache.Clone(), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, + }, nil + } + cssr := contractstaking.NewStateReader(sr) + tids, types, err := cssr.BucketTypes(s.contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get bucket types for contract %s", s.contractAddr) + } + if len(tids) != len(types) { + return nil, errors.Errorf("length of tids (%d) does not match length of types (%d)", len(tids), len(types)) + } + ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(s.contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get buckets for contract %s", s.contractAddr) + } + if len(ids) != len(buckets) { + return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) + } + cache := &contractStakingCache{} + for i, id := range tids { + if types[i] == nil { + return nil, errors.Errorf("bucket type %d is nil", id) + } + cache.PutBucketType(id, types[i]) + } + for i, id := range ids { + if buckets[i] == nil { + return nil, errors.New("bucket is nil") + } + tid, _, ok := cache.MatchBucketType(buckets[i].StakedAmount, buckets[i].StakedDuration) + if !ok { + return nil, errors.Errorf( + "no bucket type found for bucket %d with staked amount %s and duration %d", + id, + buckets[i].StakedAmount.String(), + buckets[i].StakedDuration, + ) + } + cache.PutBucketInfo(id, &bucketInfo{ + TypeIndex: tid, + CreatedAt: buckets[i].CreatedAt, + UnlockedAt: buckets[i].UnlockedAt, + UnstakedAt: buckets[i].UnstakedAt, + Delegate: buckets[i].Candidate, + Owner: buckets[i].Owner, + }) + } + return &stakeView{ - helper: s, - cache: s.cache.Clone(), - height: s.height, + cache: cache, + height: s.height, + config: s.config, + contractAddr: s.contractAddr, }, nil } @@ -133,8 +191,8 @@ func (s *Indexer) StartHeight() uint64 { } // ContractAddress returns the contract address -func (s *Indexer) ContractAddress() string { - return s.config.ContractAddress +func (s *Indexer) ContractAddress() address.Address { + return s.contractAddr } // CandidateVotes returns the candidate votes @@ -145,7 +203,7 @@ func (s *Indexer) CandidateVotes(ctx context.Context, candidate address.Address, if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.BucketsByCandidate(candidate) s.mu.RUnlock() @@ -172,10 +230,7 @@ func (s *Indexer) CandidateVotes(ctx context.Context, candidate address.Address, return votes, nil } -func (s *Indexer) genBlockDurationFn() func(start, end uint64) time.Duration { - s.mu.RLock() - height := s.height - s.mu.RUnlock() +func (s *Indexer) genBlockDurationFn(height uint64) blocksDurationFn { return func(start, end uint64) time.Duration { return s.config.BlocksToDuration(start, end, height) } @@ -189,7 +244,7 @@ func (s *Indexer) Buckets(height uint64) ([]*Bucket, error) { if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.Buckets() s.mu.RUnlock() @@ -219,7 +274,7 @@ func (s *Indexer) Bucket(id uint64, height uint64) (*Bucket, bool, error) { if err := s.validateHeight(height); err != nil { return nil, false, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() bt, bi := s.cache.Bucket(id) s.mu.RUnlock() @@ -238,7 +293,7 @@ func (s *Indexer) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ts, infos := s.cache.BucketsByIndices(indices) s.mu.RUnlock() @@ -267,7 +322,7 @@ func (s *Indexer) BucketsByCandidate(candidate address.Address, height uint64) ( if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.BucketsByCandidate(candidate) s.mu.RUnlock() @@ -327,43 +382,25 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { if blk.Height() > expectHeight { return errors.Errorf("invalid block height %d, expect %d", blk.Height(), expectHeight) } - handler, err := handleReceipts(ctx, blk.Height(), blk.Receipts, &s.config, cache) - if err != nil { - return errors.Wrapf(err, "failed to put block %d", blk.Height()) + handler := newContractStakingEventHandler(cache) + if err := handler.HandleReceipts(ctx, blk.Height(), blk.Receipts, s.contractAddr.String()); err != nil { + return errors.Wrapf(err, "failed to handle receipts at height %d", blk.Height()) } s.mu.Lock() defer s.mu.Unlock() // commit the result - if err := s.commit(handler, blk.Height()); err != nil { + if err := s.commit(ctx, handler, blk.Height()); err != nil { return errors.Wrapf(err, "failed to commit block %d", blk.Height()) } return nil } -func handleReceipts(ctx context.Context, height uint64, receipts []*action.Receipt, cfg *Config, cache stakingCache) (*contractStakingEventHandler, error) { - // new event handler for this block - handler := newContractStakingEventHandler(cache) - - // handle events of block - for _, receipt := range receipts { - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - continue - } - for _, log := range receipt.Logs() { - if log.Address != cfg.ContractAddress { - continue - } - if err := handler.HandleEvent(ctx, height, log); err != nil { - return nil, err - } - } - } - return handler, nil -} - -func (s *Indexer) commit(handler *contractStakingEventHandler, height uint64) error { +func (s *Indexer) commit(ctx context.Context, handler *contractStakingEventHandler, height uint64) error { batch, delta := handler.Result() - cache := delta.Commit() + cache, err := delta.Commit(ctx, s.contractAddr, nil) + if err != nil { + return errors.Wrapf(err, "failed to commit delta") + } base, ok := cache.(*contractStakingCache) if !ok { return errors.New("invalid cache type of base") diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index f0b846b784..fcad0fa702 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -108,7 +108,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { owner := identityset.Address(0) delegate := identityset.Address(1) stake(r, handler, owner, delegate, 1, 10, 100, height) - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) buckets, err := indexer.Buckets(height) r.NoError(err) @@ -173,7 +173,7 @@ func TestContractStakingIndexerDirty(t *testing.T) { r.NoError(err) r.EqualValues(0, gotHeight) // after commit dirty, the cache should be updated - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) gotHeight, err = indexer.Height() r.NoError(err) @@ -230,14 +230,14 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { indexer.mu.Lock() handler := newContractStakingEventHandler(indexer.cache) activateBucketType(r, handler, 10, 100, 1) - r.NoError(indexer.commit(handler, 1)) + r.NoError(indexer.commit(context.Background(), handler, 1)) indexer.mu.Unlock() for i := 2; i < 1000; i++ { height := uint64(i) indexer.mu.Lock() handler := newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, int64(i), 10, 100, height) - err := indexer.commit(handler, height) + err := indexer.commit(context.Background(), handler, height) r.NoError(err) indexer.mu.Unlock() } @@ -286,7 +286,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err := indexer.BucketTypes(height) r.NoError(err) @@ -302,7 +302,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { data := bucketTypeData[i] deactivateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -318,7 +318,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { data := bucketTypeData[i] activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -364,7 +364,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.TestDefault()), protocol.BlockCtx{BlockHeight: 1})) @@ -376,7 +376,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) @@ -402,7 +402,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, newOwner, int64(bucket.Index)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -412,7 +412,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -436,7 +436,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) lock(r, handler, int64(bucket.Index), int64(10)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -462,7 +462,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { unlock(r, handler, int64(bucket.Index), height) t.Log("unstake bucket", bucket.Index, "at height", height) unstake(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -486,7 +486,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) withdraw(r, handler, int64(bucket.Index)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.False(ok) @@ -527,7 +527,7 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) t.Run("expand bucket type", func(t *testing.T) { @@ -537,13 +537,13 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) expandBucketType(r, handler, int64(bucket.Index), 20, 100) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -581,7 +581,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) // stake @@ -602,7 +602,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { stake(r, handler, identityset.Address(data.owner), identityset.Address(data.delegate), int64(i+1), int64(data.amount), int64(data.duration), height) } r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) t.Run("Buckets", func(t *testing.T) { buckets, err := indexer.Buckets(height) @@ -693,7 +693,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.Len(ids, 0) r.Len(bts, 0) r.Len(bis, 0) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) abt = indexer.cache.ActiveBucketTypes() r.Len(abt, 2) ids, bts, bis = indexer.cache.Buckets() @@ -713,7 +713,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.NoError(err) r.True(ok) r.Equal(owner.String(), bt.Owner.String()) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bt, ok, err = indexer.Bucket(3, height) r.NoError(err) r.True(ok) @@ -757,7 +757,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate1, 2, 20, 20, height) stake(r, handler, owner, delegate2, 3, 20, 20, height) stake(r, handler, owner, delegate2, 4, 20, 20, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err := indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(30, votes.Uint64()) @@ -771,7 +771,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) changeDelegate(r, handler, delegate1, 3) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -784,7 +784,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, 1, height) unlock(r, handler, 4, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -797,7 +797,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unstake(r, handler, 1, height) lock(r, handler, 4, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(uint64(40), votes.Uint64()) @@ -809,7 +809,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) expandBucketType(r, handler, 2, 30, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -821,7 +821,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, delegate2, 4) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -835,7 +835,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 5, 20, 20, height) stake(r, handler, owner, delegate2, 6, 20, 20, height) stake(r, handler, owner, delegate2, 7, 20, 20, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -847,7 +847,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -860,7 +860,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, 5, height) unstake(r, handler, 5, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -875,7 +875,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 9, 20, 20, height) stake(r, handler, owner, delegate2, 10, 20, 20, height) mergeBuckets(r, handler, []int64{8, 9, 10}, 60, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(110, votes.Uint64()) diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index b93b000fe2..44bd1d2724 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -2,7 +2,7 @@ package contractstaking import ( "context" - "time" + "slices" "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotextypes" @@ -11,32 +11,37 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type stakeView struct { - helper *Indexer - cache stakingCache - height uint64 + contractAddr address.Address + config Config + cache stakingCache + genBlockDurationFn func(view uint64) blocksDurationFn + height uint64 } func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCache(s.cache), - height: s.height, + contractAddr: s.contractAddr, + config: s.config, + cache: newWrappedCache(s.cache), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, } } func (s *stakeView) Fork() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, + contractAddr: s.contractAddr, + cache: newWrappedCacheWithCloneInCommit(s.cache), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, } } -func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { - ids, types, infos := s.cache.BucketsByCandidate(candidate) +func (s *stakeView) assembleBuckets(ids []uint64, types []*BucketType, infos []*bucketInfo) []*Bucket { vbs := make([]*Bucket, 0, len(ids)) for i, id := range ids { bt := types[i] @@ -45,17 +50,49 @@ func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, er vbs = append(vbs, s.assembleBucket(id, info, bt)) } } - return vbs, nil + return vbs } -func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { - return assembleBucket(token, bi, bt, s.helper.config.ContractAddress, s.genBlockDurationFn(s.height)) +func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { + ids, types, infos := s.cache.Buckets() + cssm := contractstaking.NewContractStakingStateManager(sm) + bucketMap := make(map[uint64]*bucketInfo, len(ids)) + typeMap := make(map[uint64]*BucketType, len(ids)) + for i, id := range ids { + bucketMap[id] = infos[i] + typeMap[id] = types[i] + } + slices.Sort(ids) + for _, id := range ids { + info, ok := bucketMap[id] + if !ok { + continue + } + bt := typeMap[id] + if err := cssm.UpsertBucket(s.contractAddr, id, &contractstaking.Bucket{ + Candidate: info.Delegate, + Owner: info.Owner, + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: info.CreatedAt, + UnstakedAt: info.UnstakedAt, + UnlockedAt: info.UnlockedAt, + Muted: false, + IsTimestampBased: false, + }); err != nil { + return err + } + } + return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) } -func (s *stakeView) genBlockDurationFn(view uint64) blocksDurationFn { - return func(start, end uint64) time.Duration { - return s.helper.config.BlocksToDuration(start, end, view) - } +func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { + ids, types, infos := s.cache.BucketsByCandidate(candidate) + return s.assembleBuckets(ids, types, infos), nil +} + +func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { + return assembleBucket(token, bi, bt, s.contractAddr.String(), s.genBlockDurationFn(s.height)) } func (s *stakeView) CreatePreStates(ctx context.Context) error { @@ -74,7 +111,7 @@ func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { return nil } for _, log := range receipt.Logs() { - if log.Address != s.helper.config.ContractAddress { + if log.Address != s.contractAddr.String() { continue } if err := handler.HandleEvent(ctx, blkCtx.BlockHeight, log); err != nil { @@ -87,16 +124,21 @@ func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { return nil } -func (s *stakeView) Commit() { - s.cache = s.cache.Commit() +func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + cache, err := s.cache.Commit(ctx, s.contractAddr, sm) + if err != nil { + return err + } + s.cache = cache + return nil } func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight expectHeight := s.height + 1 - if expectHeight < s.helper.config.ContractDeployHeight { - expectHeight = s.helper.config.ContractDeployHeight + if expectHeight < s.config.ContractDeployHeight { + expectHeight = s.config.ContractDeployHeight } if height < expectHeight { return nil @@ -105,12 +147,12 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec return errors.Errorf("invalid block height %d, expect %d", height, expectHeight) } - handler, err := handleReceipts(ctx, height, receipts, &s.helper.config, s.cache) - if err != nil { + handler := newContractStakingEventHandler(newWrappedCache(s.cache)) + if err := handler.HandleReceipts(ctx, height, receipts, s.contractAddr.String()); err != nil { return err } _, delta := handler.Result() - s.cache = delta.Commit() + s.cache = delta s.height = height return nil } diff --git a/blockindex/contractstaking/wrappedcache.go b/blockindex/contractstaking/wrappedcache.go index 3c8301a5f5..058515d75b 100644 --- a/blockindex/contractstaking/wrappedcache.go +++ b/blockindex/contractstaking/wrappedcache.go @@ -6,11 +6,13 @@ package contractstaking import ( + "context" "math/big" "sort" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) type ( @@ -109,6 +111,27 @@ func (wc *wrappedCache) bucketType(id uint64) (*BucketType, bool) { return bt, ok } +func (wc *wrappedCache) Buckets() ([]uint64, []*BucketType, []*bucketInfo) { + wc.mu.RLock() + defer wc.mu.RUnlock() + ids, types, infos := wc.base.Buckets() + reverseMap := make(map[uint64]int, len(ids)) + for i, id := range ids { + reverseMap[id] = i + } + for id, info := range wc.updatedBucketInfos { + if i, ok := reverseMap[id]; ok { + infos[i] = info.Clone() + } else { + ids = append(ids, id) + infos = append(infos, info.Clone()) + types = append(types, wc.mustGetBucketType(info.TypeIndex)) + reverseMap[id] = len(infos) - 1 + } + } + return ids, types, infos +} + func (wc *wrappedCache) BucketsByCandidate(candidate address.Address) ([]uint64, []*BucketType, []*bucketInfo) { wc.mu.RLock() defer wc.mu.RUnlock() @@ -248,7 +271,7 @@ func (wc *wrappedCache) Clone() stakingCache { } } -func (wc *wrappedCache) Commit() stakingCache { +func (wc *wrappedCache) Commit(ctx context.Context, ca address.Address, sm protocol.StateManager) (stakingCache, error) { wc.mu.Lock() defer wc.mu.Unlock() if wc.commitWithClone { @@ -264,13 +287,13 @@ func (wc *wrappedCache) Commit() stakingCache { wc.base.PutBucketInfo(id, bi) } } - return wc.base.Commit() + return wc.base.Commit(ctx, ca, sm) } func (wc *wrappedCache) IsDirty() bool { wc.mu.RLock() defer wc.mu.RUnlock() - return len(wc.updatedBucketInfos) > 0 || len(wc.updatedBucketTypes) > 0 + return len(wc.updatedBucketInfos) > 0 || len(wc.updatedBucketTypes) > 0 || len(wc.updatedCandidates) > 0 || wc.base.IsDirty() } func (wc *wrappedCache) DeleteBucketInfo(id uint64) { diff --git a/chainservice/builder.go b/chainservice/builder.go index 812ed9c6b0..dfcd8dfc8c 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -382,9 +382,13 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } // build contract staking indexer v2 if builder.cs.contractStakingIndexerV2 == nil && len(builder.cfg.Genesis.SystemStakingContractV2Address) > 0 { + contractAddr, err := address.FromString(builder.cfg.Genesis.SystemStakingContractV2Address) + if err != nil { + return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV2Address) + } indexer := stakingindex.NewIndexer( kvstore, - builder.cfg.Genesis.SystemStakingContractV2Address, + contractAddr, builder.cfg.Genesis.SystemStakingContractV2Height, blockDurationFn, stakingindex.WithMuteHeight(builder.cfg.Genesis.WakeBlockHeight), @@ -393,9 +397,13 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } // build contract staking indexer v3 if builder.cs.contractStakingIndexerV3 == nil && len(builder.cfg.Genesis.SystemStakingContractV3Address) > 0 { + contractAddr, err := address.FromString(builder.cfg.Genesis.SystemStakingContractV3Address) + if err != nil { + return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV3Address) + } indexer := stakingindex.NewIndexer( kvstore, - builder.cfg.Genesis.SystemStakingContractV3Address, + contractAddr, builder.cfg.Genesis.SystemStakingContractV3Height, blockDurationFn, stakingindex.EnableTimestamped(), diff --git a/state/factory/statedb.go b/state/factory/statedb.go index 1e0e6613ab..de98e315fe 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -157,10 +157,6 @@ func (sdb *stateDB) Start(ctx context.Context) error { if err = sdb.dao.putHeight(0); err != nil { return errors.Wrap(err, "failed to init statedb's height") } - // start all protocols - if sdb.protocolViews, err = sdb.registry.StartAll(ctx, sdb); err != nil { - return err - } ctx = protocol.WithBlockCtx( ctx, protocol.BlockCtx{ @@ -169,6 +165,10 @@ func (sdb *stateDB) Start(ctx context.Context) error { GasLimit: sdb.cfg.Genesis.BlockGasLimitByHeight(0), }) ctx = protocol.WithFeatureCtx(ctx) + // start all protocols + if sdb.protocolViews, err = sdb.registry.StartAll(ctx, sdb); err != nil { + return err + } // init the state factory if err = sdb.createGenesisStates(ctx); err != nil { return errors.Wrap(err, "failed to create genesis states") @@ -244,8 +244,7 @@ func (sdb *stateDB) newWorkingSetWithKVStore(ctx context.Context, height uint64, if err := store.Start(ctx); err != nil { return nil, err } - views := sdb.protocolViews.Fork() - return newWorkingSet(height, views, store, sdb), nil + return newWorkingSet(height, sdb.protocolViews.Fork(), store, sdb), nil } func (sdb *stateDB) CreateWorkingSetStore(ctx context.Context, height uint64, kvstore db.KVStore) (workingSetStore, error) { diff --git a/state/factory/util.go b/state/factory/util.go index 157e1b3c6c..24ae4e9fba 100644 --- a/state/factory/util.go +++ b/state/factory/util.go @@ -103,12 +103,12 @@ func protocolPreCommit(ctx context.Context, sr protocol.StateManager) error { return nil } -func protocolCommit(ctx context.Context, sr protocol.StateManager) error { +func protocolCommit(ctx context.Context, sm protocol.StateManager) error { if reg, ok := protocol.GetRegistry(ctx); ok { for _, p := range reg.All() { post, ok := p.(protocol.Committer) if ok { - if err := post.Commit(ctx, sr); err != nil { + if err := post.Commit(ctx, sm); err != nil { return err } } diff --git a/state/factory/workingset.go b/state/factory/workingset.go index ddfb1949de..4497cbff11 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -940,6 +940,9 @@ func (ws *workingSet) ValidateBlock(ctx context.Context, blk *block.Block) error log.L().Error("Failed to update state.", zap.Uint64("height", ws.height), zap.Error(err)) return err } + if err := ws.views.Commit(ctx, ws); err != nil { + return err + } digest, err := ws.digest() if err != nil { @@ -966,6 +969,9 @@ func (ws *workingSet) CreateBuilder( if err != nil { return nil, err } + if err := ws.views.Commit(ctx, ws); err != nil { + return nil, err + } var ( blkCtx = protocol.MustGetBlockCtx(ctx) diff --git a/state/factory/workingset_test.go b/state/factory/workingset_test.go index f5c621de66..dd6c5cf837 100644 --- a/state/factory/workingset_test.go +++ b/state/factory/workingset_test.go @@ -78,7 +78,7 @@ func (v mockView) Revert(int) error { return nil } -func (v mockView) Commit(context.Context, protocol.StateReader) error { +func (v mockView) Commit(context.Context, protocol.StateManager) error { return nil } diff --git a/systemcontractindex/common.go b/systemcontractindex/common.go index 0f0c14cbb5..17484e8eee 100644 --- a/systemcontractindex/common.go +++ b/systemcontractindex/common.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" + "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" @@ -22,12 +23,12 @@ type IndexerCommon struct { key []byte startHeight uint64 height uint64 - contractAddress string + contractAddress address.Address lifecycle.Readiness } // NewIndexerCommon creates a new IndexerCommon -func NewIndexerCommon(kvstore db.KVStore, ns string, key []byte, contractAddress string, startHeight uint64) *IndexerCommon { +func NewIndexerCommon(kvstore db.KVStore, ns string, key []byte, contractAddress address.Address, startHeight uint64) *IndexerCommon { return &IndexerCommon{ kvstore: kvstore, ns: ns, @@ -69,7 +70,7 @@ func (s *IndexerCommon) Stop(ctx context.Context) error { func (s *IndexerCommon) KVStore() db.KVStore { return s.kvstore } // ContractAddress returns the contract address -func (s *IndexerCommon) ContractAddress() string { return s.contractAddress } +func (s *IndexerCommon) ContractAddress() address.Address { return s.contractAddress } // Height returns the tip block height func (s *IndexerCommon) Height() uint64 { diff --git a/systemcontractindex/stakingindex/bucket.go b/systemcontractindex/stakingindex/bucket.go index 1c6b4ad10c..8a292912a0 100644 --- a/systemcontractindex/stakingindex/bucket.go +++ b/systemcontractindex/stakingindex/bucket.go @@ -1,21 +1,20 @@ package stakingindex import ( - "math/big" "time" "github.com/iotexproject/iotex-address/address" - "github.com/pkg/errors" - "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" - "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex/stakingpb" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type VoteBucket = staking.VoteBucket -type Bucket struct { +type Bucket = contractstaking.Bucket + +/* +struct { Candidate address.Address Owner address.Address StakedAmount *big.Int @@ -102,6 +101,7 @@ func (b *Bucket) Clone() *Bucket { clone.StakedAmount = new(big.Int).Set(b.StakedAmount) return clone } +*/ func assembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksToDurationFn blocksDurationFn) *VoteBucket { vb := VoteBucket{ @@ -111,9 +111,9 @@ func assembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksTo Candidate: bkt.Candidate, Owner: bkt.Owner, ContractAddress: contractAddr, - Timestamped: bkt.Timestamped, + Timestamped: bkt.IsTimestampBased, } - if bkt.Timestamped { + if bkt.IsTimestampBased { vb.StakedDuration = time.Duration(bkt.StakedDuration) * time.Second vb.StakeStartTime = time.Unix(int64(bkt.CreatedAt), 0) vb.CreateTime = time.Unix(int64(bkt.CreatedAt), 0) diff --git a/systemcontractindex/stakingindex/bucket_test.go b/systemcontractindex/stakingindex/bucket_test.go index 7c5dc3c0f7..879633db76 100644 --- a/systemcontractindex/stakingindex/bucket_test.go +++ b/systemcontractindex/stakingindex/bucket_test.go @@ -20,20 +20,22 @@ func TestBucket_SerializeDeserialize(t *testing.T) { owner := identityset.Address(2) original := &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(123456), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(123456), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: true, } - data := original.Serialize() + data, err := original.Serialize() + r.NoError(err, "Serialize failed") var deserialized Bucket r.NoError(deserialized.Deserialize(data), "Deserialize failed") + deserialized.IsTimestampBased = original.IsTimestampBased // IsTimestampBased is not serialized, so we set it manually r.Equal(*original, deserialized) } @@ -42,15 +44,15 @@ func TestBucket_Clone(t *testing.T) { candidate := identityset.Address(1) owner := identityset.Address(2) original := &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(123456), - Muted: true, - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(123456), + Muted: true, + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, } clone := original.Clone() @@ -71,48 +73,48 @@ func TestAssembleVoteBucket(t *testing.T) { bucket *Bucket }{ {"timestamped", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(1000), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: false, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(1000), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: false, }}, {"timestamped/muted", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(1000), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(1000), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: true, }}, {"block-based", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(2000), - Timestamped: false, - StakedDuration: 100, - CreatedAt: 10, - UnlockedAt: 20, - UnstakedAt: 110, - Muted: false, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(2000), + IsTimestampBased: false, + StakedDuration: 100, + CreatedAt: 10, + UnlockedAt: 20, + UnstakedAt: 110, + Muted: false, }}, {"block-based/muted", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(2000), - Timestamped: false, - StakedDuration: 100, - CreatedAt: 10, - UnlockedAt: 20, - UnstakedAt: 110, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(2000), + IsTimestampBased: false, + StakedDuration: 100, + CreatedAt: 10, + UnlockedAt: 20, + UnstakedAt: 110, + Muted: true, }}, } @@ -124,7 +126,7 @@ func TestAssembleVoteBucket(t *testing.T) { if c.bucket.Muted { expectCandidate, _ = address.FromString(address.ZeroAddress) } - if c.bucket.Timestamped { + if c.bucket.IsTimestampBased { stakeStartTime := time.Unix(int64(c.bucket.CreatedAt), 0) unstakeStartTime := time.Unix(0, 0) if c.bucket.UnlockedAt != maxStakingNumber { diff --git a/systemcontractindex/stakingindex/cache.go b/systemcontractindex/stakingindex/cache.go index d8912272a4..8882f7c53e 100644 --- a/systemcontractindex/stakingindex/cache.go +++ b/systemcontractindex/stakingindex/cache.go @@ -1,11 +1,14 @@ package stakingindex import ( + "context" "errors" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -20,7 +23,7 @@ type ( BucketIdsByCandidate(candidate address.Address) []uint64 TotalBucketCount() uint64 Clone() indexerCache - Commit() indexerCache + Commit(context.Context, address.Address, bool, protocol.StateManager) (indexerCache, error) IsDirty() bool } // base is the in-memory base for staking index @@ -30,6 +33,7 @@ type ( bucketsByCandidate map[string]map[uint64]struct{} totalBucketCount uint64 mu sync.RWMutex + delta map[uint64]*Bucket } ) @@ -37,6 +41,7 @@ func newCache() *base { return &base{ buckets: make(map[uint64]*Bucket), bucketsByCandidate: make(map[string]map[uint64]struct{}), + delta: make(map[uint64]*Bucket), } } @@ -84,6 +89,15 @@ func (s *base) Clone() indexerCache { } } c.totalBucketCount = s.totalBucketCount + + for k, v := range s.delta { + if v == nil { + c.delta[k] = nil + } else { + c.delta[k] = v.Clone() + } + } + return c } @@ -105,6 +119,8 @@ func (s *base) PutBucket(id uint64, bkt *Bucket) { s.bucketsByCandidate[cand] = make(map[uint64]struct{}) } s.bucketsByCandidate[cand][id] = struct{}{} + + s.delta[id] = bkt } func (s *base) DeleteBucket(id uint64) { @@ -120,6 +136,8 @@ func (s *base) DeleteBucket(id uint64) { delete(s.bucketsByCandidate, cand) } delete(s.buckets, id) + + s.delta[id] = nil } func (s *base) BucketIdxs() []uint64 { @@ -176,8 +194,26 @@ func (s *base) IsDirty() bool { return false } -func (s *base) Commit() indexerCache { +func (s *base) Commit(ctx context.Context, ca address.Address, timestamp bool, sm protocol.StateManager) (indexerCache, error) { s.mu.Lock() defer s.mu.Unlock() - return s + if sm == nil { + s.delta = make(map[uint64]*Bucket) + return s, nil + } + cssm := contractstaking.NewContractStakingStateManager(sm) + for id, bkt := range s.delta { + if bkt == nil { + if err := cssm.DeleteBucket(ca, id); err != nil { + return nil, err + } + } else { + bkt.IsTimestampBased = timestamp + if err := cssm.UpsertBucket(ca, id, bkt); err != nil { + return nil, err + } + } + } + s.delta = make(map[uint64]*Bucket) + return s, nil } diff --git a/systemcontractindex/stakingindex/cache_test.go b/systemcontractindex/stakingindex/cache_test.go new file mode 100644 index 0000000000..7b6a579a58 --- /dev/null +++ b/systemcontractindex/stakingindex/cache_test.go @@ -0,0 +1,74 @@ +package stakingindex + +import ( + "context" + "math/big" + "reflect" + "testing" + + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" + "github.com/stretchr/testify/require" +) + +func TestBase(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cache := newCache() + t.Run("empty", func(t *testing.T) { + require.Equal(0, len(cache.BucketIdxs())) + require.False(cache.IsDirty()) + }) + t.Run("clone", func(t *testing.T) { + clone := cache.Clone() + if clone == cache { + t.Error("Expected clone to be a different instance") + } + if !reflect.DeepEqual(clone, cache) { + t.Error("Expected clone to be equal to original") + } + }) + t.Run("put bucket", func(t *testing.T) { + require.Nil(cache.Bucket(1)) + bkt1 := &Bucket{ + Candidate: identityset.Address(1), + Owner: identityset.Address(2), + StakedAmount: big.NewInt(1000), + StakedDuration: 3600, + CreatedAt: 123456, + } + bkt2 := &Bucket{ + Candidate: identityset.Address(3), + Owner: identityset.Address(4), + StakedAmount: big.NewInt(2000), + StakedDuration: 7200, + CreatedAt: 654321, + } + cache.PutBucket(1, bkt1) + cache.PutBucket(2, bkt2) + require.Equal(2, len(cache.BucketIdxs())) + require.Equal(bkt1, cache.Bucket(1)) + require.Equal(bkt2, cache.Bucket(2)) + bkts := cache.Buckets([]uint64{1, 2, 3}) + require.Equal(2, len(bkts)) + require.Equal(bkt1, bkts[0]) + require.Equal(bkt2, bkts[1]) + require.Equal([]uint64{1}, cache.BucketIdsByCandidate(identityset.Address(1))) + require.Equal([]uint64{2}, cache.BucketIdsByCandidate(identityset.Address(3))) + t.Run("delete bucket", func(t *testing.T) { + cache.DeleteBucket(1) + require.Nil(cache.Bucket(1)) + require.Equal(1, len(cache.BucketIdxs())) + t.Run("commit", func(t *testing.T) { + sm := mock_chainmanager.NewMockStateManager(ctrl) + sm.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), nil).Times(1) + sm.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil).Times(1) + _, err := cache.Commit(context.Background(), identityset.Address(10), false, sm) + require.NoError(err) + }) + }) + }) +} diff --git a/systemcontractindex/stakingindex/event_handler.go b/systemcontractindex/stakingindex/event_handler.go index 5b79dff2b4..8b383b205a 100644 --- a/systemcontractindex/stakingindex/event_handler.go +++ b/systemcontractindex/stakingindex/event_handler.go @@ -1,17 +1,23 @@ package stakingindex import ( + "context" _ "embed" "math" "strings" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "go.uber.org/zap" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/db/batch" + "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/util/abiutil" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -87,15 +93,15 @@ func (eh *eventHandler) HandleStakedEvent(event *abiutil.EventParam) error { createdAt = uint64(eh.blockCtx.BlockTimeStamp.Unix()) } bucket := &Bucket{ - Candidate: delegateParam, - Owner: owner, - StakedAmount: amountParam, - StakedDuration: durationParam.Uint64(), - CreatedAt: createdAt, - UnlockedAt: maxStakingNumber, - UnstakedAt: maxStakingNumber, - Timestamped: eh.timestamped, - Muted: eh.muted, + Candidate: delegateParam, + Owner: owner, + StakedAmount: amountParam, + StakedDuration: durationParam.Uint64(), + CreatedAt: createdAt, + UnlockedAt: maxStakingNumber, + UnstakedAt: maxStakingNumber, + IsTimestampBased: eh.timestamped, + Muted: eh.muted, } eh.putBucket(tokenIDParam.Uint64(), bucket) return nil @@ -291,11 +297,74 @@ func (eh *eventHandler) Finalize() (batch.KVStoreBatch, indexerCache) { } func (eh *eventHandler) putBucket(id uint64, bkt *Bucket) { + data, err := bkt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket")) + } eh.dirty.PutBucket(id, bkt) - eh.delta.Put(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), bkt.Serialize(), "failed to put bucket") + eh.delta.Put(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket") } func (eh *eventHandler) delBucket(id uint64) { eh.dirty.DeleteBucket(id) eh.delta.Delete(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), "failed to delete bucket") } + +func (eh *eventHandler) handleReceipt(ctx context.Context, contractAddr string, receipt *action.Receipt) error { + if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { + return nil + } + for _, log := range receipt.Logs() { + if log.Address != contractAddr { + continue + } + if err := eh.handleEvent(ctx, log); err != nil { + return err + } + } + return nil +} + +func (eh *eventHandler) handleEvent(ctx context.Context, actLog *action.Log) error { + // get event abi + abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) + if err != nil { + return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) + } + + // unpack event data + event, err := abiutil.UnpackEventParam(abiEvent, actLog) + if err != nil { + return err + } + log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) + // handle different kinds of event + switch abiEvent.Name { + case "Staked": + return eh.HandleStakedEvent(event) + case "Locked": + return eh.HandleLockedEvent(event) + case "Unlocked": + return eh.HandleUnlockedEvent(event) + case "Unstaked": + return eh.HandleUnstakedEvent(event) + case "Merged": + return eh.HandleMergedEvent(event) + case "BucketExpanded": + return eh.HandleBucketExpandedEvent(event) + case "DelegateChanged": + return eh.HandleDelegateChangedEvent(event) + case "Withdrawal": + return eh.HandleWithdrawalEvent(event) + case "Donated": + return eh.HandleDonatedEvent(event) + case "Transfer": + return eh.HandleTransferEvent(event) + case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", + "Migrated": + // not require handling events + return nil + default: + return errors.Errorf("unknown event name %s", abiEvent.Name) + } +} diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 7f69c9fca0..00327cebc0 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -5,15 +5,13 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/common" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "go.uber.org/zap" - "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/db/batch" @@ -39,14 +37,14 @@ type ( lifecycle.StartStopper Height() (uint64, error) StartHeight() uint64 - ContractAddress() string + ContractAddress() address.Address Buckets(height uint64) ([]*VoteBucket, error) Bucket(id uint64, height uint64) (*VoteBucket, bool, error) BucketsByIndices(indices []uint64, height uint64) ([]*VoteBucket, error) BucketsByCandidate(candidate address.Address, height uint64) ([]*VoteBucket, error) TotalBucketCount(height uint64) (uint64, error) PutBlock(ctx context.Context, blk *block.Block) error - StartView(ctx context.Context) (staking.ContractStakeView, error) + LoadStakeView(context.Context, protocol.StateReader) (staking.ContractStakeView, error) } stakingEventHandler interface { HandleStakedEvent(event *abiutil.EventParam) error @@ -94,9 +92,9 @@ func EnableTimestamped() IndexerOption { } // NewIndexer creates a new staking indexer -func NewIndexer(kvstore db.KVStore, contractAddr string, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { - bucketNS := contractAddr + "#" + stakingBucketNS - ns := contractAddr + "#" + stakingNS +func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { + bucketNS := contractAddr.String() + "#" + stakingBucketNS + ns := contractAddr.String() + "#" + stakingNS idx := &Indexer{ common: systemcontractindex.NewIndexerCommon(kvstore, ns, stakingHeightKey, contractAddr, startHeight), cache: newCache(), @@ -120,21 +118,6 @@ func (s *Indexer) Start(ctx context.Context) error { return s.start(ctx) } -func (s *Indexer) StartView(ctx context.Context) (staking.ContractStakeView, error) { - s.mutex.Lock() - defer s.mutex.Unlock() - if !s.common.Started() { - if err := s.start(ctx); err != nil { - return nil, err - } - } - return &stakeView{ - helper: s, - cache: s.cache.Clone(), - height: s.common.Height(), - }, nil -} - func (s *Indexer) start(ctx context.Context) error { if err := s.common.Start(ctx); err != nil { return err @@ -149,6 +132,55 @@ func (s *Indexer) Stop(ctx context.Context) error { return s.common.Stop(ctx) } +// LoadStakeView loads the contract stake view from state reader +func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { + s.mutex.RLock() + defer s.mutex.RUnlock() + if !s.common.Started() { + if err := s.start(ctx); err != nil { + return nil, err + } + } + if protocol.MustGetFeatureCtx(ctx).LoadContractStakingFromIndexer { + return &stakeView{ + cache: s.cache.Clone(), + height: s.common.Height(), + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + timestamped: s.timestamped, + startHeight: s.common.StartHeight(), + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, + }, nil + } + contractAddr := s.common.ContractAddress() + ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get buckets for contract %s", contractAddr) + } + if len(ids) != len(buckets) { + return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) + } + cache := &base{} + for i, b := range buckets { + if b == nil { + return nil, errors.New("bucket is nil") + } + b.IsTimestampBased = s.timestamped + cache.PutBucket(ids[i], b) + } + return &stakeView{ + cache: cache, + height: s.common.Height(), + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + startHeight: s.common.StartHeight(), + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, + }, nil +} + // Height returns the tip block height func (s *Indexer) Height() (uint64, error) { s.mutex.RLock() @@ -164,7 +196,7 @@ func (s *Indexer) StartHeight() uint64 { } // ContractAddress returns the contract address -func (s *Indexer) ContractAddress() string { +func (s *Indexer) ContractAddress() address.Address { s.mutex.RLock() defer s.mutex.RUnlock() return s.common.ContractAddress() @@ -182,7 +214,7 @@ func (s *Indexer) Buckets(height uint64) ([]*VoteBucket, error) { } idxs := s.cache.BucketIdxs() bkts := s.cache.Buckets(idxs) - vbs := batchAssembleVoteBucket(idxs, bkts, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(idxs, bkts, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -200,7 +232,7 @@ func (s *Indexer) Bucket(id uint64, height uint64) (*VoteBucket, bool, error) { if bkt == nil { return nil, false, nil } - vbs := assembleVoteBucket(id, bkt, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := assembleVoteBucket(id, bkt, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, true, nil } @@ -215,7 +247,7 @@ func (s *Indexer) BucketsByIndices(indices []uint64, height uint64) ([]*VoteBuck return nil, nil } bkts := s.cache.Buckets(indices) - vbs := batchAssembleVoteBucket(indices, bkts, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(indices, bkts, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -240,7 +272,7 @@ func (s *Indexer) BucketsByCandidate(candidate address.Address, height uint64) ( bktsFiltered = append(bktsFiltered, bkts[i]) } } - vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -273,85 +305,30 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { muted := s.muteHeight > 0 && blk.Height() >= s.muteHeight handler := newEventHandler(s.bucketNS, newWrappedCache(s.cache), protocol.MustGetBlockCtx(ctx), s.timestamped, muted) for _, receipt := range blk.Receipts { - if err := s.handleReceipt(ctx, handler, receipt); err != nil { + if err := handler.handleReceipt(ctx, s.common.ContractAddress().String(), receipt); err != nil { return errors.Wrapf(err, "handle receipt %x failed", receipt.ActionHash) } } // commit - return s.commit(handler, blk.Height()) -} - -func (s *Indexer) handleReceipt(ctx context.Context, eh stakingEventHandler, receipt *action.Receipt) error { - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - return nil - } - for _, log := range receipt.Logs() { - if log.Address != s.common.ContractAddress() { - continue - } - if err := s.handleEvent(ctx, eh, log); err != nil { - return err - } - } - return nil + return s.commit(ctx, handler, blk.Height()) } -func (s *Indexer) handleEvent(ctx context.Context, eh stakingEventHandler, actLog *action.Log) error { - // get event abi - abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) - if err != nil { - return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) - } - - // unpack event data - event, err := abiutil.UnpackEventParam(abiEvent, actLog) - if err != nil { - return err - } - log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) - // handle different kinds of event - switch abiEvent.Name { - case "Staked": - return eh.HandleStakedEvent(event) - case "Locked": - return eh.HandleLockedEvent(event) - case "Unlocked": - return eh.HandleUnlockedEvent(event) - case "Unstaked": - return eh.HandleUnstakedEvent(event) - case "Merged": - return eh.HandleMergedEvent(event) - case "BucketExpanded": - return eh.HandleBucketExpandedEvent(event) - case "DelegateChanged": - return eh.HandleDelegateChangedEvent(event) - case "Withdrawal": - return eh.HandleWithdrawalEvent(event) - case "Donated": - return eh.HandleDonatedEvent(event) - case "Transfer": - return eh.HandleTransferEvent(event) - case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", - "Migrated": - // not require handling events - return nil - default: - return errors.Errorf("unknown event name %s", abiEvent.Name) - } -} - -func (s *Indexer) commit(handler stakingEventHandler, height uint64) error { +func (s *Indexer) commit(ctx context.Context, handler stakingEventHandler, height uint64) error { delta, dirty := handler.Finalize() // update db if err := s.common.Commit(height, delta); err != nil { return err } - cache, ok := dirty.Commit().(*base) + cache, err := dirty.Commit(ctx, s.common.ContractAddress(), s.timestamped, nil) + if err != nil { + return errors.Wrapf(err, "failed to commit dirty cache at height %d", height) + } + base, ok := cache.(*base) if !ok { return errors.Errorf("unexpected cache type %T, expect *base", dirty) } // update cache - s.cache = cache + s.cache = base return nil } diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 274b15cbd6..829a478cf6 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -2,6 +2,7 @@ package stakingindex import ( "context" + "slices" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" @@ -9,30 +10,59 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type stakeView struct { - helper *Indexer - cache indexerCache - height uint64 + cache indexerCache + height uint64 + startHeight uint64 + contractAddr address.Address + muteHeight uint64 + timestamped bool + bucketNS string + genBlockDurationFn func(view uint64) blocksDurationFn } func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCache(s.cache), - height: s.height, + cache: newWrappedCache(s.cache), + height: s.height, + startHeight: s.startHeight, + contractAddr: s.contractAddr, + muteHeight: s.muteHeight, + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, } } func (s *stakeView) Fork() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, + cache: newWrappedCacheWithCloneInCommit(s.cache), + height: s.height, + startHeight: s.startHeight, + contractAddr: s.contractAddr, + muteHeight: s.muteHeight, + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, } } +func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { + ids := s.cache.BucketIdxs() + slices.Sort(ids) + buckets := s.cache.Buckets(ids) + cssm := contractstaking.NewContractStakingStateManager(sm) + for _, id := range ids { + if err := cssm.UpsertBucket(s.contractAddr, id, buckets[id]); err != nil { + return err + } + } + return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) +} + func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket, error) { idxs := s.cache.BucketIdsByCandidate(candidate) bkts := s.cache.Buckets(idxs) @@ -45,7 +75,7 @@ func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket bktsFiltered = append(bktsFiltered, bkts[i]) } } - vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.helper.common.ContractAddress(), s.helper.genBlockDurationFn(s.height)) + vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.contractAddr.String(), s.genBlockDurationFn(s.height)) return vbs, nil } @@ -57,25 +87,25 @@ func (s *stakeView) CreatePreStates(ctx context.Context) error { func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) - muted := s.helper.muteHeight > 0 && blkCtx.BlockHeight >= s.helper.muteHeight - handler := newEventHandler(s.helper.bucketNS, s.cache, blkCtx, s.helper.timestamped, muted) - return s.helper.handleReceipt(ctx, handler, receipt) + muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight + handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) + return handler.handleReceipt(ctx, s.contractAddr.String(), receipt) } func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight - if height < s.helper.common.StartHeight() { + if height < s.startHeight { return nil } - if height != s.height+1 && height != s.helper.StartHeight() { + if height != s.height+1 && height != s.startHeight { return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) } ctx = protocol.WithBlockCtx(ctx, blkCtx) - muted := s.helper.muteHeight > 0 && height >= s.helper.muteHeight - handler := newEventHandler(s.helper.bucketNS, s.cache, blkCtx, s.helper.timestamped, muted) + muted := s.muteHeight > 0 && height >= s.muteHeight + handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) for _, receipt := range receipts { - if err := s.helper.handleReceipt(ctx, handler, receipt); err != nil { + if err := handler.handleReceipt(ctx, s.contractAddr.String(), receipt); err != nil { return errors.Wrapf(err, "failed to handle receipt at height %d", height) } } @@ -83,6 +113,12 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec return nil } -func (s *stakeView) Commit() { - s.cache = s.cache.Commit() +func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + cache, err := s.cache.Commit(ctx, s.contractAddr, s.timestamped, sm) + if err != nil { + return err + } + s.cache = cache + + return nil } diff --git a/systemcontractindex/stakingindex/wrappedcache.go b/systemcontractindex/stakingindex/wrappedcache.go index 6d1a91077c..274c87caf3 100644 --- a/systemcontractindex/stakingindex/wrappedcache.go +++ b/systemcontractindex/stakingindex/wrappedcache.go @@ -1,10 +1,12 @@ package stakingindex import ( + "context" "slices" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) type wrappedCache struct { @@ -14,6 +16,7 @@ type wrappedCache struct { mu sync.RWMutex commitWithClone bool // whether to commit with deep clone + readOnly bool } func newWrappedCache(cache indexerCache) *wrappedCache { @@ -36,6 +39,9 @@ func newWrappedCacheWithCloneInCommit(cache indexerCache) *wrappedCache { func (w *wrappedCache) PutBucket(id uint64, bkt *Bucket) { w.mu.Lock() defer w.mu.Unlock() + if w.readOnly { + panic("cannot delete bucket in read-only mode") + } oldBucket, ok := w.updatedBuckets[id] if !ok { oldBucket = w.cache.Bucket(id) @@ -58,6 +64,9 @@ func (w *wrappedCache) PutBucket(id uint64, bkt *Bucket) { func (w *wrappedCache) DeleteBucket(id uint64) { w.mu.Lock() defer w.mu.Unlock() + if w.readOnly { + panic("cannot delete bucket in read-only mode") + } w.updatedBuckets[id] = nil } @@ -158,7 +167,7 @@ func (w *wrappedCache) TotalBucketCount() uint64 { func (w *wrappedCache) IsDirty() bool { w.mu.RLock() defer w.mu.RUnlock() - return w.cache.IsDirty() || w.isDirty() + return w.isDirty() || w.cache.IsDirty() } func (w *wrappedCache) Clone() indexerCache { @@ -180,29 +189,29 @@ func (w *wrappedCache) Clone() indexerCache { } } wc.commitWithClone = w.commitWithClone + wc.readOnly = w.readOnly return wc } -func (w *wrappedCache) Commit() indexerCache { +func (w *wrappedCache) Commit(ctx context.Context, ca address.Address, timestamp bool, sm protocol.StateManager) (indexerCache, error) { w.mu.Lock() defer w.mu.Unlock() if w.isDirty() { - cache := w.cache if w.commitWithClone { - cache = w.cache.Clone() + w.cache = w.cache.Clone() } for id, bkt := range w.updatedBuckets { if bkt == nil { - cache.DeleteBucket(id) + w.cache.DeleteBucket(id) } else { - cache.PutBucket(id, bkt) + w.cache.PutBucket(id, bkt) } } w.updatedBuckets = make(map[uint64]*Bucket) w.bucketsByCandidate = make(map[string]map[uint64]bool) - w.cache = cache } - return w.cache.Commit() + w.readOnly = true + return w.cache.Commit(ctx, ca, timestamp, sm) } func (w *wrappedCache) isDirty() bool { From a95f8a2a99fbfb7525acd0f714e6e2d617de76ad Mon Sep 17 00:00:00 2001 From: CoderZhi Date: Wed, 3 Sep 2025 03:51:40 +0300 Subject: [PATCH 13/34] snapshot/revert views (#4669) --- action/protocol/protocol.go | 38 ++++++++++++++++++- action/protocol/staking/viewdata.go | 17 ++++++++- blockindex/contractstaking/stakeview.go | 4 ++ blockindex/contractstaking/wrappedcache.go | 5 +++ e2etest/expect.go | 4 +- state/factory/workingset.go | 18 ++++++++- systemcontractindex/stakingindex/stakeview.go | 4 ++ 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/action/protocol/protocol.go b/action/protocol/protocol.go index 85d59fa297..f2c96bf070 100644 --- a/action/protocol/protocol.go +++ b/action/protocol/protocol.go @@ -121,16 +121,50 @@ type ( // Views stores the view for all protocols Views struct { - vm map[string]View + snapshotID int + snapshots map[int]map[string]int + vm map[string]View } ) func NewViews() *Views { return &Views{ - vm: make(map[string]View), + snapshotID: 0, + snapshots: make(map[int]map[string]int), + vm: make(map[string]View), } } +func (views *Views) Snapshot() int { + views.snapshotID++ + views.snapshots[views.snapshotID] = make(map[string]int) + keys := make([]string, 0, len(views.vm)) + for key := range views.vm { + keys = append(keys, key) + } + for _, key := range keys { + views.snapshots[views.snapshotID][key] = views.vm[key].Snapshot() + } + return views.snapshotID +} + +func (views *Views) Revert(id int) error { + if id > views.snapshotID || id < 0 { + return errors.Errorf("invalid snapshot id %d, max id is %d", id, views.snapshotID) + } + for k, v := range views.snapshots[id] { + if err := views.vm[k].Revert(v); err != nil { + return err + } + } + views.snapshotID = id + // clean up snapshots that are not needed anymore + for i := id + 1; i <= views.snapshotID; i++ { + delete(views.snapshots, i) + } + return nil +} + func (views *Views) Fork() *Views { fork := NewViews() for key, view := range views.vm { diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index 108b33bf6d..ece4958849 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -22,6 +22,8 @@ type ( Wrap() ContractStakeView // Fork forks the contract stake view, commit will not affect the original view Fork() ContractStakeView + // IsDirty checks if the contract stake view is dirty + IsDirty() bool // Commit commits the contract stake view Commit(context.Context, protocol.StateManager) error // CreatePreStates creates pre states for the contract stake view @@ -91,7 +93,7 @@ func (v *viewData) Commit(ctx context.Context, sm protocol.StateManager) error { } func (v *viewData) IsDirty() bool { - return v.candCenter.IsDirty() || v.bucketPool.IsDirty() + return v.candCenter.IsDirty() || v.bucketPool.IsDirty() || (v.contractsStake != nil && v.contractsStake.IsDirty()) } func (v *viewData) Snapshot() int { @@ -198,6 +200,19 @@ func (csv *contractStakeView) CreatePreStates(ctx context.Context) error { return nil } +func (csv *contractStakeView) IsDirty() bool { + if csv.v1 != nil && csv.v1.IsDirty() { + return true + } + if csv.v2 != nil && csv.v2.IsDirty() { + return true + } + if csv.v3 != nil && csv.v3.IsDirty() { + return true + } + return false +} + func (csv *contractStakeView) Commit(ctx context.Context, sm protocol.StateManager) error { featureCtx, ok := protocol.GetFeatureCtx(ctx) if !ok || featureCtx.LoadContractStakingFromIndexer { diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index 44bd1d2724..6de2803567 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -53,6 +53,10 @@ func (s *stakeView) assembleBuckets(ids []uint64, types []*BucketType, infos []* return vbs } +func (s *stakeView) IsDirty() bool { + return s.cache.IsDirty() +} + func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { ids, types, infos := s.cache.Buckets() cssm := contractstaking.NewContractStakingStateManager(sm) diff --git a/blockindex/contractstaking/wrappedcache.go b/blockindex/contractstaking/wrappedcache.go index 058515d75b..226e6ea074 100644 --- a/blockindex/contractstaking/wrappedcache.go +++ b/blockindex/contractstaking/wrappedcache.go @@ -287,6 +287,11 @@ func (wc *wrappedCache) Commit(ctx context.Context, ca address.Address, sm proto wc.base.PutBucketInfo(id, bi) } } + wc.updatedBucketInfos = make(map[uint64]*bucketInfo) + wc.updatedBucketTypes = make(map[uint64]*BucketType) + wc.updatedCandidates = make(map[string]map[uint64]bool) + wc.propertyBucketTypeMap = make(map[uint64]map[uint64]uint64) + return wc.base.Commit(ctx, ca, sm) } diff --git a/e2etest/expect.go b/e2etest/expect.go index 2b4fe31d8e..b81e8de105 100644 --- a/e2etest/expect.go +++ b/e2etest/expect.go @@ -97,9 +97,7 @@ func (ce *candidateExpect) expect(test *e2etest, act *action.SealedEnvelope, rec cs := test.svr.ChainService(test.cfg.Chain.ID) sr := cs.StateFactory() bc := cs.Blockchain() - prtcl, ok := cs.Registry().Find("staking") - require.True(ok) - stkPrtcl := prtcl.(*staking.Protocol) + stkPrtcl := staking.FindProtocol(cs.Registry()) reqBytes, err := proto.Marshal(r) require.NoError(err) ctx := protocol.WithRegistry(context.Background(), cs.Registry()) diff --git a/state/factory/workingset.go b/state/factory/workingset.go index 4497cbff11..7b065c9113 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -71,6 +71,7 @@ type ( workingSetStoreFactory WorkingSetStoreFactory height uint64 views *protocol.Views + viewsSnapshots map[int]int store workingSetStore finalized bool txValidator *protocol.GenericValidator @@ -82,6 +83,7 @@ func newWorkingSet(height uint64, views *protocol.Views, store workingSetStore, ws := &workingSet{ height: height, views: views, + viewsSnapshots: make(map[int]int), store: store, workingSetStoreFactory: storeFactory, } @@ -281,14 +283,28 @@ func (ws *workingSet) finalizeTx(ctx context.Context) { } func (ws *workingSet) Snapshot() int { - return ws.store.Snapshot() + id := ws.store.Snapshot() + vid := ws.views.Snapshot() + ws.viewsSnapshots[id] = vid + + return id } func (ws *workingSet) Revert(snapshot int) error { + vid, ok := ws.viewsSnapshots[snapshot] + if !ok { + return errors.Errorf("snapshot %d not found", snapshot) + } + if err := ws.views.Revert(vid); err != nil { + return errors.Wrapf(err, "failed to revert views to snapshot %d", vid) + } return ws.store.RevertSnapshot(snapshot) } func (ws *workingSet) ResetSnapshots() { + if len(ws.viewsSnapshots) > 0 { + ws.viewsSnapshots = make(map[int]int) + } ws.store.ResetSnapshots() } diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 829a478cf6..dd49e80278 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -50,6 +50,10 @@ func (s *stakeView) Fork() staking.ContractStakeView { } } +func (s *stakeView) IsDirty() bool { + return s.cache.IsDirty() +} + func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { ids := s.cache.BucketIdxs() slices.Sort(ids) From 2bea890ca2e972ab945337596436d3014d29beb6 Mon Sep 17 00:00:00 2001 From: zhi Date: Mon, 1 Sep 2025 23:55:44 +0800 Subject: [PATCH 14/34] fix two unit tests and refactor matchbuckettype --- blockindex/contractstaking/cache.go | 8 ++++---- blockindex/contractstaking/cache_test.go | 20 ++++++++----------- blockindex/contractstaking/dirty_cache.go | 6 +++--- .../contractstaking/dirty_cache_test.go | 11 +++++----- blockindex/contractstaking/event_handler.go | 20 +++++++++---------- blockindex/contractstaking/indexer.go | 4 ++-- blockindex/contractstaking/wrappedcache.go | 12 +++++------ 7 files changed, 38 insertions(+), 43 deletions(-) diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index 68ca596fda..fad4b39af4 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -25,7 +25,7 @@ type ( BucketInfo(id uint64) (*bucketInfo, bool) MustGetBucketInfo(id uint64) *bucketInfo MustGetBucketType(id uint64) *BucketType - MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) + MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) BucketType(id uint64) (*BucketType, bool) BucketTypeCount() int Buckets() ([]uint64, []*BucketType, []*bucketInfo) @@ -210,15 +210,15 @@ func (s *contractStakingCache) DeleteBucketInfo(id uint64) { s.deltaBuckets[id] = nil } -func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { +func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) { s.mutex.RLock() defer s.mutex.RUnlock() id, ok := s.getBucketTypeIndex(amount, duration) if !ok { - return 0, nil, false + return 0, nil } - return id, s.mustGetBucketType(id), true + return id, s.mustGetBucketType(id) } func (s *contractStakingCache) LoadFromDB(kvstore db.KVStore) error { diff --git a/blockindex/contractstaking/cache_test.go b/blockindex/contractstaking/cache_test.go index f96d609908..41893b0276 100644 --- a/blockindex/contractstaking/cache_test.go +++ b/blockindex/contractstaking/cache_test.go @@ -376,33 +376,29 @@ func TestContractStakingCache_MatchBucketType(t *testing.T) { cache := newContractStakingCache() // no bucket types - _, bucketType, ok := cache.MatchBucketType(big.NewInt(100), 100) - require.False(ok) + _, bucketType := cache.MatchBucketType(big.NewInt(100), 100) require.Nil(bucketType) // one bucket type cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) // match exact bucket type - id, bucketType, ok := cache.MatchBucketType(big.NewInt(100), 100) - require.True(ok) + id, bucketType := cache.MatchBucketType(big.NewInt(100), 100) + require.NotNil(bucketType) require.EqualValues(1, id) require.EqualValues(100, bucketType.Amount.Int64()) require.EqualValues(100, bucketType.Duration) require.EqualValues(1, bucketType.ActivatedAt) // match bucket type with different amount - _, bucketType, ok = cache.MatchBucketType(big.NewInt(200), 100) - require.False(ok) + _, bucketType = cache.MatchBucketType(big.NewInt(200), 100) require.Nil(bucketType) // match bucket type with different duration - _, bucketType, ok = cache.MatchBucketType(big.NewInt(100), 200) - require.False(ok) + _, bucketType = cache.MatchBucketType(big.NewInt(100), 200) require.Nil(bucketType) // no match - _, bucketType, ok = cache.MatchBucketType(big.NewInt(200), 200) - require.False(ok) + _, bucketType = cache.MatchBucketType(big.NewInt(200), 200) require.Nil(bucketType) } @@ -533,8 +529,8 @@ func TestContractStakingCache_LoadFromDB(t *testing.T) { require.Equal(bucketInfo, bi) btc = cache.BucketTypeCount() require.EqualValues(1, btc) - id, bt, ok := cache.MatchBucketType(big.NewInt(100), 100) - require.True(ok) + id, bt := cache.MatchBucketType(big.NewInt(100), 100) + require.NotNil(bt) require.EqualValues(1, id) require.EqualValues(100, bt.Amount.Int64()) require.EqualValues(100, bt.Duration) diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index 8f2ce90972..88a04cd0c3 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -73,8 +73,8 @@ func (dirty *contractStakingDirty) deleteBucketInfo(id uint64) { } func (dirty *contractStakingDirty) putBucketType(bt *BucketType) { - id, _, ok := dirty.matchBucketType(bt.Amount, bt.Duration) - if !ok { + id, old := dirty.matchBucketType(bt.Amount, bt.Duration) + if old == nil { id = dirty.getBucketTypeCount() dirty.addBucketType(id, bt) } @@ -112,7 +112,7 @@ func (dirty *contractStakingDirty) addBucketType(id uint64, bt *BucketType) { dirty.cache.PutBucketType(id, bt) } -func (dirty *contractStakingDirty) matchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { +func (dirty *contractStakingDirty) matchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) { return dirty.cache.MatchBucketType(amount, duration) } diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index b6070a28d6..9d6736bb37 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -96,15 +96,14 @@ func TestContractStakingDirty_matchBucketType(t *testing.T) { dirty := newContractStakingDirty(clean) // no bucket type - id, bt, ok := dirty.matchBucketType(big.NewInt(100), 100) - require.False(ok) + id, bt := dirty.matchBucketType(big.NewInt(100), 100) require.Nil(bt) require.EqualValues(0, id) // bucket type in clean cache clean.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) - id, bt, ok = dirty.matchBucketType(big.NewInt(100), 100) - require.True(ok) + id, bt = dirty.matchBucketType(big.NewInt(100), 100) + require.NotNil(bt) require.EqualValues(100, bt.Amount.Int64()) require.EqualValues(100, bt.Duration) require.EqualValues(1, bt.ActivatedAt) @@ -112,8 +111,8 @@ func TestContractStakingDirty_matchBucketType(t *testing.T) { // added bucket type dirty.addBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) - id, bt, ok = dirty.matchBucketType(big.NewInt(200), 200) - require.True(ok) + id, bt = dirty.matchBucketType(big.NewInt(200), 200) + require.NotNil(bt) require.EqualValues(200, bt.Amount.Int64()) require.EqualValues(200, bt.Duration) require.EqualValues(2, bt.ActivatedAt) diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go index c9b4aae092..abcea1db29 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/event_handler.go @@ -491,8 +491,8 @@ func (eh *contractStakingEventHandler) handleBucketTypeDeactivatedEvent(event ev return err } - id, bt, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if !ok { + id, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) + if bt == nil { return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) } bt.ActivatedAt = maxBlockNumber @@ -519,8 +519,8 @@ func (eh *contractStakingEventHandler) handleStakedEvent(event eventParam, heigh return err } - btIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if !ok { + btIdx, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) + if bt == nil { return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) } owner, ok := eh.tokenOwner[tokenIDParam.Uint64()] @@ -557,8 +557,8 @@ func (eh *contractStakingEventHandler) handleLockedEvent(event eventParam) error if !ok { return errors.Wrapf(errBucketTypeNotExist, "id %d", b.TypeIndex) } - newBtIdx, _, ok := eh.dirty.matchBucketType(bt.Amount, durationParam.Uint64()) - if !ok { + newBtIdx, newBt := eh.dirty.matchBucketType(bt.Amount, durationParam.Uint64()) + if newBt == nil { return errors.Wrapf(errBucketTypeNotExist, "amount %v, duration %d", bt.Amount, durationParam.Uint64()) } b.TypeIndex = newBtIdx @@ -615,8 +615,8 @@ func (eh *contractStakingEventHandler) handleMergedEvent(event eventParam) error } // merge to the first bucket - btIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if !ok { + btIdx, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) + if bt == nil { return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) } b, ok := eh.dirty.getBucketInfo(tokenIDsParam[0].Uint64()) @@ -651,8 +651,8 @@ func (eh *contractStakingEventHandler) handleBucketExpandedEvent(event eventPara if !ok { return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) } - newBtIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if !ok { + newBtIdx, newBucketType := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) + if newBucketType == nil { return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) } b.TypeIndex = newBtIdx diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index d9adc6dfc8..7e21fe5bf2 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -128,8 +128,8 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s if buckets[i] == nil { return nil, errors.New("bucket is nil") } - tid, _, ok := cache.MatchBucketType(buckets[i].StakedAmount, buckets[i].StakedDuration) - if !ok { + tid, bt := cache.MatchBucketType(buckets[i].StakedAmount, buckets[i].StakedDuration) + if bt == nil { return nil, errors.Errorf( "no bucket type found for bucket %d with staked amount %s and duration %d", id, diff --git a/blockindex/contractstaking/wrappedcache.go b/blockindex/contractstaking/wrappedcache.go index 226e6ea074..7ce7f15324 100644 --- a/blockindex/contractstaking/wrappedcache.go +++ b/blockindex/contractstaking/wrappedcache.go @@ -198,8 +198,8 @@ func (wc *wrappedCache) PutBucketType(id uint64, bt *BucketType) { panic("bucket type amount or duration cannot be changed") } } - oldId, _, ok := wc.matchBucketType(bt.Amount, bt.Duration) - if ok && oldId != id { + oldId, oldBucketType := wc.matchBucketType(bt.Amount, bt.Duration) + if oldBucketType != nil && oldId != id { panic("bucket type with same amount and duration already exists") } if _, ok := wc.propertyBucketTypeMap[bt.Amount.Uint64()]; !ok { @@ -313,21 +313,21 @@ func (wc *wrappedCache) DeleteBucketInfo(id uint64) { wc.updatedBucketInfos[id] = nil } -func (wc *wrappedCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { +func (wc *wrappedCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) { wc.mu.RLock() defer wc.mu.RUnlock() return wc.matchBucketType(amount, duration) } -func (wc *wrappedCache) matchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { +func (wc *wrappedCache) matchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) { amountUint64 := amount.Uint64() if amountMap, ok := wc.propertyBucketTypeMap[amountUint64]; ok { if id, ok := amountMap[duration]; ok { if bt, ok := wc.updatedBucketTypes[id]; ok { if bt != nil { - return id, bt, true + return id, bt } - return 0, nil, false + return 0, nil } } } From 6e70d2e8d6b16bba0352cc0852eb4c0a55ae42e6 Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 2 Sep 2025 00:25:23 +0800 Subject: [PATCH 15/34] introduce event processor --- action/protocol/mock_protocol.go | 28 +- .../protocol/staking/contractstake_indexer.go | 17 +- .../staking/contractstake_indexer_mock.go | 154 ++++++++ .../staking/contractstaking/bucket.go | 3 + api/mock_apicoreservice.go | 144 ++++---- blockindex/contractstaking/dirty_cache.go | 62 +++- .../contractstaking/dirty_cache_test.go | 6 +- .../{event_handler.go => eventprocessor.go} | 233 ++++++------ blockindex/contractstaking/indexer.go | 17 +- blockindex/contractstaking/indexer_test.go | 169 +++++---- blockindex/contractstaking/stakeview.go | 28 +- .../stakingindex/event_handler.go | 346 +---------------- .../stakingindex/eventprocessor.go | 347 ++++++++++++++++++ systemcontractindex/stakingindex/index.go | 45 +-- systemcontractindex/stakingindex/stakeview.go | 14 +- .../mock_chainmanager/mock_chainmanager.go | 187 ++++++++++ 16 files changed, 1132 insertions(+), 668 deletions(-) rename blockindex/contractstaking/{event_handler.go => eventprocessor.go} (63%) create mode 100644 systemcontractindex/stakingindex/eventprocessor.go diff --git a/action/protocol/mock_protocol.go b/action/protocol/mock_protocol.go index ebdf0ed9dd..e9ea48eaf7 100644 --- a/action/protocol/mock_protocol.go +++ b/action/protocol/mock_protocol.go @@ -488,20 +488,6 @@ func (m *MockView) EXPECT() *MockViewMockRecorder { return m.recorder } -// Clone mocks base method. -func (m *MockView) Clone() View { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Clone") - ret0, _ := ret[0].(View) - return ret0 -} - -// Clone indicates an expected call of Clone. -func (mr *MockViewMockRecorder) Clone() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clone", reflect.TypeOf((*MockView)(nil).Clone)) -} - // Commit mocks base method. func (m *MockView) Commit(arg0 context.Context, arg1 StateManager) error { m.ctrl.T.Helper() @@ -516,6 +502,20 @@ func (mr *MockViewMockRecorder) Commit(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockView)(nil).Commit), arg0, arg1) } +// Fork mocks base method. +func (m *MockView) Fork() View { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fork") + ret0, _ := ret[0].(View) + return ret0 +} + +// Fork indicates an expected call of Fork. +func (mr *MockViewMockRecorder) Fork() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fork", reflect.TypeOf((*MockView)(nil).Fork)) +} + // Revert mocks base method. func (m *MockView) Revert(arg0 int) error { m.ctrl.T.Helper() diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index 4d8568389e..ebe0b1952f 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -13,7 +13,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) var ( @@ -25,7 +27,18 @@ var ( ) type ( - + // EventHandler is the interface for handling staking events + EventHandler interface { + PutBucketType(address.Address, *ContractStakingBucketType) error + DeductBucket(address.Address, uint64) (*contractstaking.Bucket, error) + PutBucket(address.Address, uint64, *contractstaking.Bucket) error + DeleteBucket(address.Address, uint64) error + } + // EventProcessor is the interface for processing staking events + EventProcessor interface { + // ProcessReceipts processes receipts + ProcessReceipts(context.Context, ...*action.Receipt) error + } // ContractStakingIndexer defines the interface of contract staking reader ContractStakingIndexer interface { Height() (uint64, error) @@ -41,6 +54,8 @@ type ( ContractAddress() address.Address // LoadStakeView loads the contract stake view from state reader LoadStakeView(context.Context, protocol.StateReader) (ContractStakeView, error) + // CreateEventProcessor creates a new event processor + CreateEventProcessor(context.Context, EventHandler) EventProcessor } // ContractStakingIndexerWithBucketType defines the interface of contract staking reader with bucket type ContractStakingIndexerWithBucketType interface { diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index 1c049bc48d..23991ee16f 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -14,10 +14,136 @@ import ( reflect "reflect" address "github.com/iotexproject/iotex-address/address" + action "github.com/iotexproject/iotex-core/v2/action" protocol "github.com/iotexproject/iotex-core/v2/action/protocol" + contractstaking "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" gomock "go.uber.org/mock/gomock" ) +// MockEventHandler is a mock of EventHandler interface. +type MockEventHandler struct { + ctrl *gomock.Controller + recorder *MockEventHandlerMockRecorder + isgomock struct{} +} + +// MockEventHandlerMockRecorder is the mock recorder for MockEventHandler. +type MockEventHandlerMockRecorder struct { + mock *MockEventHandler +} + +// NewMockEventHandler creates a new mock instance. +func NewMockEventHandler(ctrl *gomock.Controller) *MockEventHandler { + mock := &MockEventHandler{ctrl: ctrl} + mock.recorder = &MockEventHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventHandler) EXPECT() *MockEventHandlerMockRecorder { + return m.recorder +} + +// DeductBucket mocks base method. +func (m *MockEventHandler) DeductBucket(arg0 address.Address, arg1 uint64) (*contractstaking.Bucket, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeductBucket", arg0, arg1) + ret0, _ := ret[0].(*contractstaking.Bucket) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeductBucket indicates an expected call of DeductBucket. +func (mr *MockEventHandlerMockRecorder) DeductBucket(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeductBucket", reflect.TypeOf((*MockEventHandler)(nil).DeductBucket), arg0, arg1) +} + +// DeleteBucket mocks base method. +func (m *MockEventHandler) DeleteBucket(arg0 address.Address, arg1 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBucket", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBucket indicates an expected call of DeleteBucket. +func (mr *MockEventHandlerMockRecorder) DeleteBucket(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBucket", reflect.TypeOf((*MockEventHandler)(nil).DeleteBucket), arg0, arg1) +} + +// PutBucket mocks base method. +func (m *MockEventHandler) PutBucket(arg0 address.Address, arg1 uint64, arg2 *contractstaking.Bucket) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutBucket", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutBucket indicates an expected call of PutBucket. +func (mr *MockEventHandlerMockRecorder) PutBucket(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutBucket", reflect.TypeOf((*MockEventHandler)(nil).PutBucket), arg0, arg1, arg2) +} + +// PutBucketType mocks base method. +func (m *MockEventHandler) PutBucketType(arg0 address.Address, arg1 *ContractStakingBucketType) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutBucketType", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutBucketType indicates an expected call of PutBucketType. +func (mr *MockEventHandlerMockRecorder) PutBucketType(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutBucketType", reflect.TypeOf((*MockEventHandler)(nil).PutBucketType), arg0, arg1) +} + +// MockEventProcessor is a mock of EventProcessor interface. +type MockEventProcessor struct { + ctrl *gomock.Controller + recorder *MockEventProcessorMockRecorder + isgomock struct{} +} + +// MockEventProcessorMockRecorder is the mock recorder for MockEventProcessor. +type MockEventProcessorMockRecorder struct { + mock *MockEventProcessor +} + +// NewMockEventProcessor creates a new mock instance. +func NewMockEventProcessor(ctrl *gomock.Controller) *MockEventProcessor { + mock := &MockEventProcessor{ctrl: ctrl} + mock.recorder = &MockEventProcessorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventProcessor) EXPECT() *MockEventProcessorMockRecorder { + return m.recorder +} + +// ProcessReceipts mocks base method. +func (m *MockEventProcessor) ProcessReceipts(arg0 context.Context, arg1 ...*action.Receipt) error { + m.ctrl.T.Helper() + varargs := []any{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ProcessReceipts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// ProcessReceipts indicates an expected call of ProcessReceipts. +func (mr *MockEventProcessorMockRecorder) ProcessReceipts(arg0 any, arg1 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessReceipts", reflect.TypeOf((*MockEventProcessor)(nil).ProcessReceipts), varargs...) +} + // MockContractStakingIndexer is a mock of ContractStakingIndexer interface. type MockContractStakingIndexer struct { ctrl *gomock.Controller @@ -101,6 +227,20 @@ func (mr *MockContractStakingIndexerMockRecorder) ContractAddress() *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContractAddress", reflect.TypeOf((*MockContractStakingIndexer)(nil).ContractAddress)) } +// CreateEventProcessor mocks base method. +func (m *MockContractStakingIndexer) CreateEventProcessor(arg0 context.Context, arg1 EventHandler) EventProcessor { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEventProcessor", arg0, arg1) + ret0, _ := ret[0].(EventProcessor) + return ret0 +} + +// CreateEventProcessor indicates an expected call of CreateEventProcessor. +func (mr *MockContractStakingIndexerMockRecorder) CreateEventProcessor(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEventProcessor", reflect.TypeOf((*MockContractStakingIndexer)(nil).CreateEventProcessor), arg0, arg1) +} + // Height mocks base method. func (m *MockContractStakingIndexer) Height() (uint64, error) { m.ctrl.T.Helper() @@ -244,6 +384,20 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) ContractAddress( return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContractAddress", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).ContractAddress)) } +// CreateEventProcessor mocks base method. +func (m *MockContractStakingIndexerWithBucketType) CreateEventProcessor(arg0 context.Context, arg1 EventHandler) EventProcessor { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEventProcessor", arg0, arg1) + ret0, _ := ret[0].(EventProcessor) + return ret0 +} + +// CreateEventProcessor indicates an expected call of CreateEventProcessor. +func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) CreateEventProcessor(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEventProcessor", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).CreateEventProcessor), arg0, arg1) +} + // Height mocks base method. func (m *MockContractStakingIndexerWithBucketType) Height() (uint64, error) { m.ctrl.T.Helper() diff --git a/action/protocol/staking/contractstaking/bucket.go b/action/protocol/staking/contractstaking/bucket.go index 91429ed37c..703becee88 100644 --- a/action/protocol/staking/contractstaking/bucket.go +++ b/action/protocol/staking/contractstaking/bucket.go @@ -35,6 +35,9 @@ type ( } ) +// ErrBucketNotExist is the error when bucket does not exist +var ErrBucketNotExist = errors.New("bucket does not exist") + func (b *Bucket) toProto() *stakingpb.SystemStakingBucket { if b == nil { return nil diff --git a/api/mock_apicoreservice.go b/api/mock_apicoreservice.go index fa073c4496..d7c0e241f1 100644 --- a/api/mock_apicoreservice.go +++ b/api/mock_apicoreservice.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: ./api/coreservice.go +// +// Generated by this command: +// +// mockgen -destination=./api/mock_apicoreservice.go -source=./api/coreservice.go -package=api CoreService +// // Package api is a generated GoMock package. package api @@ -13,22 +18,22 @@ import ( tracers "github.com/ethereum/go-ethereum/eth/tracers" hash "github.com/iotexproject/go-pkgs/hash" address "github.com/iotexproject/iotex-address/address" - iotexapi "github.com/iotexproject/iotex-proto/golang/iotexapi" - iotextypes "github.com/iotexproject/iotex-proto/golang/iotextypes" - gomock "go.uber.org/mock/gomock" - action "github.com/iotexproject/iotex-core/v2/action" protocol "github.com/iotexproject/iotex-core/v2/action/protocol" logfilter "github.com/iotexproject/iotex-core/v2/api/logfilter" - types "github.com/iotexproject/iotex-core/v2/api/types" + apitypes "github.com/iotexproject/iotex-core/v2/api/types" block "github.com/iotexproject/iotex-core/v2/blockchain/block" genesis "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + iotexapi "github.com/iotexproject/iotex-proto/golang/iotexapi" + iotextypes "github.com/iotexproject/iotex-proto/golang/iotextypes" + gomock "go.uber.org/mock/gomock" ) // MockCoreService is a mock of CoreService interface. type MockCoreService struct { ctrl *gomock.Controller recorder *MockCoreServiceMockRecorder + isgomock struct{} } // MockCoreServiceMockRecorder is the mock recorder for MockCoreService. @@ -59,7 +64,7 @@ func (m *MockCoreService) Account(addr address.Address) (*iotextypes.AccountMeta } // Account indicates an expected call of Account. -func (mr *MockCoreServiceMockRecorder) Account(addr interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Account(addr any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Account", reflect.TypeOf((*MockCoreService)(nil).Account), addr) } @@ -74,7 +79,7 @@ func (m *MockCoreService) Action(actionHash string, checkPending bool) (*iotexap } // Action indicates an expected call of Action. -func (mr *MockCoreServiceMockRecorder) Action(actionHash, checkPending interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Action(actionHash, checkPending any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Action", reflect.TypeOf((*MockCoreService)(nil).Action), actionHash, checkPending) } @@ -91,7 +96,7 @@ func (m *MockCoreService) ActionByActionHash(h hash.Hash256) (*action.SealedEnve } // ActionByActionHash indicates an expected call of ActionByActionHash. -func (mr *MockCoreServiceMockRecorder) ActionByActionHash(h interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ActionByActionHash(h any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActionByActionHash", reflect.TypeOf((*MockCoreService)(nil).ActionByActionHash), h) } @@ -106,7 +111,7 @@ func (m *MockCoreService) Actions(start, count uint64) ([]*iotexapi.ActionInfo, } // Actions indicates an expected call of Actions. -func (mr *MockCoreServiceMockRecorder) Actions(start, count interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Actions(start, count any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Actions", reflect.TypeOf((*MockCoreService)(nil).Actions), start, count) } @@ -121,7 +126,7 @@ func (m *MockCoreService) ActionsByAddress(addr address.Address, start, count ui } // ActionsByAddress indicates an expected call of ActionsByAddress. -func (mr *MockCoreServiceMockRecorder) ActionsByAddress(addr, start, count interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ActionsByAddress(addr, start, count any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActionsByAddress", reflect.TypeOf((*MockCoreService)(nil).ActionsByAddress), addr, start, count) } @@ -136,7 +141,7 @@ func (m *MockCoreService) ActionsInActPool(actHashes []string) ([]*action.Sealed } // ActionsInActPool indicates an expected call of ActionsInActPool. -func (mr *MockCoreServiceMockRecorder) ActionsInActPool(actHashes interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ActionsInActPool(actHashes any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActionsInActPool", reflect.TypeOf((*MockCoreService)(nil).ActionsInActPool), actHashes) } @@ -151,67 +156,67 @@ func (m *MockCoreService) BalanceAt(ctx context.Context, addr address.Address, h } // BalanceAt indicates an expected call of BalanceAt. -func (mr *MockCoreServiceMockRecorder) BalanceAt(ctx, addr, height interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BalanceAt(ctx, addr, height any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BalanceAt", reflect.TypeOf((*MockCoreService)(nil).BalanceAt), ctx, addr, height) } // BlobSidecarsByHeight mocks base method. -func (m *MockCoreService) BlobSidecarsByHeight(height uint64) ([]*types.BlobSidecarResult, error) { +func (m *MockCoreService) BlobSidecarsByHeight(height uint64) ([]*apitypes.BlobSidecarResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlobSidecarsByHeight", height) - ret0, _ := ret[0].([]*types.BlobSidecarResult) + ret0, _ := ret[0].([]*apitypes.BlobSidecarResult) ret1, _ := ret[1].(error) return ret0, ret1 } // BlobSidecarsByHeight indicates an expected call of BlobSidecarsByHeight. -func (mr *MockCoreServiceMockRecorder) BlobSidecarsByHeight(height interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BlobSidecarsByHeight(height any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlobSidecarsByHeight", reflect.TypeOf((*MockCoreService)(nil).BlobSidecarsByHeight), height) } // BlockByHash mocks base method. -func (m *MockCoreService) BlockByHash(arg0 string) (*types.BlockWithReceipts, error) { +func (m *MockCoreService) BlockByHash(arg0 string) (*apitypes.BlockWithReceipts, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlockByHash", arg0) - ret0, _ := ret[0].(*types.BlockWithReceipts) + ret0, _ := ret[0].(*apitypes.BlockWithReceipts) ret1, _ := ret[1].(error) return ret0, ret1 } // BlockByHash indicates an expected call of BlockByHash. -func (mr *MockCoreServiceMockRecorder) BlockByHash(arg0 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BlockByHash(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHash", reflect.TypeOf((*MockCoreService)(nil).BlockByHash), arg0) } // BlockByHeight mocks base method. -func (m *MockCoreService) BlockByHeight(arg0 uint64) (*types.BlockWithReceipts, error) { +func (m *MockCoreService) BlockByHeight(arg0 uint64) (*apitypes.BlockWithReceipts, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlockByHeight", arg0) - ret0, _ := ret[0].(*types.BlockWithReceipts) + ret0, _ := ret[0].(*apitypes.BlockWithReceipts) ret1, _ := ret[1].(error) return ret0, ret1 } // BlockByHeight indicates an expected call of BlockByHeight. -func (mr *MockCoreServiceMockRecorder) BlockByHeight(arg0 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BlockByHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHeight", reflect.TypeOf((*MockCoreService)(nil).BlockByHeight), arg0) } // BlockByHeightRange mocks base method. -func (m *MockCoreService) BlockByHeightRange(arg0, arg1 uint64) ([]*types.BlockWithReceipts, error) { +func (m *MockCoreService) BlockByHeightRange(arg0, arg1 uint64) ([]*apitypes.BlockWithReceipts, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlockByHeightRange", arg0, arg1) - ret0, _ := ret[0].([]*types.BlockWithReceipts) + ret0, _ := ret[0].([]*apitypes.BlockWithReceipts) ret1, _ := ret[1].(error) return ret0, ret1 } // BlockByHeightRange indicates an expected call of BlockByHeightRange. -func (mr *MockCoreServiceMockRecorder) BlockByHeightRange(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BlockByHeightRange(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByHeightRange", reflect.TypeOf((*MockCoreService)(nil).BlockByHeightRange), arg0, arg1) } @@ -226,7 +231,7 @@ func (m *MockCoreService) BlockHashByBlockHeight(blkHeight uint64) (hash.Hash256 } // BlockHashByBlockHeight indicates an expected call of BlockHashByBlockHeight. -func (mr *MockCoreServiceMockRecorder) BlockHashByBlockHeight(blkHeight interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) BlockHashByBlockHeight(blkHeight any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockHashByBlockHeight", reflect.TypeOf((*MockCoreService)(nil).BlockHashByBlockHeight), blkHeight) } @@ -246,10 +251,10 @@ func (mr *MockCoreServiceMockRecorder) ChainID() *gomock.Call { } // ChainListener mocks base method. -func (m *MockCoreService) ChainListener() types.Listener { +func (m *MockCoreService) ChainListener() apitypes.Listener { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChainListener") - ret0, _ := ret[0].(types.Listener) + ret0, _ := ret[0].(apitypes.Listener) return ret0 } @@ -285,7 +290,7 @@ func (m *MockCoreService) CodeAt(ctx context.Context, addr address.Address, heig } // CodeAt indicates an expected call of CodeAt. -func (mr *MockCoreServiceMockRecorder) CodeAt(ctx, addr, height interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) CodeAt(ctx, addr, height any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CodeAt", reflect.TypeOf((*MockCoreService)(nil).CodeAt), ctx, addr, height) } @@ -314,7 +319,7 @@ func (m *MockCoreService) ElectionBuckets(epochNum uint64) ([]*iotextypes.Electi } // ElectionBuckets indicates an expected call of ElectionBuckets. -func (mr *MockCoreServiceMockRecorder) ElectionBuckets(epochNum interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ElectionBuckets(epochNum any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ElectionBuckets", reflect.TypeOf((*MockCoreService)(nil).ElectionBuckets), epochNum) } @@ -331,7 +336,7 @@ func (m *MockCoreService) EpochMeta(epochNum uint64) (*iotextypes.EpochData, uin } // EpochMeta indicates an expected call of EpochMeta. -func (mr *MockCoreServiceMockRecorder) EpochMeta(epochNum interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EpochMeta(epochNum any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EpochMeta", reflect.TypeOf((*MockCoreService)(nil).EpochMeta), epochNum) } @@ -339,7 +344,7 @@ func (mr *MockCoreServiceMockRecorder) EpochMeta(epochNum interface{}) *gomock.C // EstimateExecutionGasConsumption mocks base method. func (m *MockCoreService) EstimateExecutionGasConsumption(ctx context.Context, sc action.Envelope, callerAddr address.Address, opts ...protocol.SimulateOption) (uint64, []byte, error) { m.ctrl.T.Helper() - varargs := []interface{}{ctx, sc, callerAddr} + varargs := []any{ctx, sc, callerAddr} for _, a := range opts { varargs = append(varargs, a) } @@ -351,16 +356,16 @@ func (m *MockCoreService) EstimateExecutionGasConsumption(ctx context.Context, s } // EstimateExecutionGasConsumption indicates an expected call of EstimateExecutionGasConsumption. -func (mr *MockCoreServiceMockRecorder) EstimateExecutionGasConsumption(ctx, sc, callerAddr interface{}, opts ...interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateExecutionGasConsumption(ctx, sc, callerAddr any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, sc, callerAddr}, opts...) + varargs := append([]any{ctx, sc, callerAddr}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateExecutionGasConsumption", reflect.TypeOf((*MockCoreService)(nil).EstimateExecutionGasConsumption), varargs...) } // EstimateExecutionGasConsumptionAt mocks base method. func (m *MockCoreService) EstimateExecutionGasConsumptionAt(ctx context.Context, sc action.Envelope, callerAddr address.Address, height uint64, opts ...protocol.SimulateOption) (uint64, []byte, error) { m.ctrl.T.Helper() - varargs := []interface{}{ctx, sc, callerAddr, height} + varargs := []any{ctx, sc, callerAddr, height} for _, a := range opts { varargs = append(varargs, a) } @@ -372,9 +377,9 @@ func (m *MockCoreService) EstimateExecutionGasConsumptionAt(ctx context.Context, } // EstimateExecutionGasConsumptionAt indicates an expected call of EstimateExecutionGasConsumptionAt. -func (mr *MockCoreServiceMockRecorder) EstimateExecutionGasConsumptionAt(ctx, sc, callerAddr, height interface{}, opts ...interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateExecutionGasConsumptionAt(ctx, sc, callerAddr, height any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, sc, callerAddr, height}, opts...) + varargs := append([]any{ctx, sc, callerAddr, height}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateExecutionGasConsumptionAt", reflect.TypeOf((*MockCoreService)(nil).EstimateExecutionGasConsumptionAt), varargs...) } @@ -388,7 +393,7 @@ func (m *MockCoreService) EstimateGasForAction(ctx context.Context, in *iotextyp } // EstimateGasForAction indicates an expected call of EstimateGasForAction. -func (mr *MockCoreServiceMockRecorder) EstimateGasForAction(ctx, in interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateGasForAction(ctx, in any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGasForAction", reflect.TypeOf((*MockCoreService)(nil).EstimateGasForAction), ctx, in) } @@ -403,7 +408,7 @@ func (m *MockCoreService) EstimateGasForNonExecution(arg0 action.Action) (uint64 } // EstimateGasForNonExecution indicates an expected call of EstimateGasForNonExecution. -func (mr *MockCoreServiceMockRecorder) EstimateGasForNonExecution(arg0 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateGasForNonExecution(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGasForNonExecution", reflect.TypeOf((*MockCoreService)(nil).EstimateGasForNonExecution), arg0) } @@ -419,7 +424,7 @@ func (m *MockCoreService) EstimateMigrateStakeGasConsumption(arg0 context.Contex } // EstimateMigrateStakeGasConsumption indicates an expected call of EstimateMigrateStakeGasConsumption. -func (mr *MockCoreServiceMockRecorder) EstimateMigrateStakeGasConsumption(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateMigrateStakeGasConsumption(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateMigrateStakeGasConsumption", reflect.TypeOf((*MockCoreService)(nil).EstimateMigrateStakeGasConsumption), arg0, arg1, arg2) } @@ -435,7 +440,7 @@ func (m *MockCoreService) EstimateMigrateStakeGasConsumptionAt(arg0 context.Cont } // EstimateMigrateStakeGasConsumptionAt indicates an expected call of EstimateMigrateStakeGasConsumptionAt. -func (mr *MockCoreServiceMockRecorder) EstimateMigrateStakeGasConsumptionAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) EstimateMigrateStakeGasConsumptionAt(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateMigrateStakeGasConsumptionAt", reflect.TypeOf((*MockCoreService)(nil).EstimateMigrateStakeGasConsumptionAt), arg0, arg1, arg2, arg3) } @@ -455,7 +460,7 @@ func (m *MockCoreService) FeeHistory(ctx context.Context, blocks, lastBlock uint } // FeeHistory indicates an expected call of FeeHistory. -func (mr *MockCoreServiceMockRecorder) FeeHistory(ctx, blocks, lastBlock, rewardPercentiles interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) FeeHistory(ctx, blocks, lastBlock, rewardPercentiles any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FeeHistory", reflect.TypeOf((*MockCoreService)(nil).FeeHistory), ctx, blocks, lastBlock, rewardPercentiles) } @@ -484,7 +489,7 @@ func (m *MockCoreService) LogsInBlockByHash(filter *logfilter.LogFilter, blockHa } // LogsInBlockByHash indicates an expected call of LogsInBlockByHash. -func (mr *MockCoreServiceMockRecorder) LogsInBlockByHash(filter, blockHash interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) LogsInBlockByHash(filter, blockHash any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogsInBlockByHash", reflect.TypeOf((*MockCoreService)(nil).LogsInBlockByHash), filter, blockHash) } @@ -500,7 +505,7 @@ func (m *MockCoreService) LogsInRange(filter *logfilter.LogFilter, start, end, p } // LogsInRange indicates an expected call of LogsInRange. -func (mr *MockCoreServiceMockRecorder) LogsInRange(filter, start, end, paginationSize interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) LogsInRange(filter, start, end, paginationSize any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogsInRange", reflect.TypeOf((*MockCoreService)(nil).LogsInRange), filter, start, end, paginationSize) } @@ -515,7 +520,7 @@ func (m *MockCoreService) PendingActionByActionHash(h hash.Hash256) (*action.Sea } // PendingActionByActionHash indicates an expected call of PendingActionByActionHash. -func (mr *MockCoreServiceMockRecorder) PendingActionByActionHash(h interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) PendingActionByActionHash(h any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingActionByActionHash", reflect.TypeOf((*MockCoreService)(nil).PendingActionByActionHash), h) } @@ -530,7 +535,7 @@ func (m *MockCoreService) PendingNonce(arg0 address.Address) (uint64, error) { } // PendingNonce indicates an expected call of PendingNonce. -func (mr *MockCoreServiceMockRecorder) PendingNonce(arg0 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) PendingNonce(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingNonce", reflect.TypeOf((*MockCoreService)(nil).PendingNonce), arg0) } @@ -545,7 +550,7 @@ func (m *MockCoreService) PendingNonceAt(ctx context.Context, addr address.Addre } // PendingNonceAt indicates an expected call of PendingNonceAt. -func (mr *MockCoreServiceMockRecorder) PendingNonceAt(ctx, addr, height interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) PendingNonceAt(ctx, addr, height any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingNonceAt", reflect.TypeOf((*MockCoreService)(nil).PendingNonceAt), ctx, addr, height) } @@ -560,7 +565,7 @@ func (m *MockCoreService) RawBlocks(startHeight, count uint64, withReceipts, wit } // RawBlocks indicates an expected call of RawBlocks. -func (mr *MockCoreServiceMockRecorder) RawBlocks(startHeight, count, withReceipts, withTransactionLogs interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) RawBlocks(startHeight, count, withReceipts, withTransactionLogs any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RawBlocks", reflect.TypeOf((*MockCoreService)(nil).RawBlocks), startHeight, count, withReceipts, withTransactionLogs) } @@ -576,7 +581,7 @@ func (m *MockCoreService) ReadContract(ctx context.Context, callerAddr address.A } // ReadContract indicates an expected call of ReadContract. -func (mr *MockCoreServiceMockRecorder) ReadContract(ctx, callerAddr, sc interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReadContract(ctx, callerAddr, sc any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadContract", reflect.TypeOf((*MockCoreService)(nil).ReadContract), ctx, callerAddr, sc) } @@ -591,7 +596,7 @@ func (m *MockCoreService) ReadContractStorage(ctx context.Context, addr address. } // ReadContractStorage indicates an expected call of ReadContractStorage. -func (mr *MockCoreServiceMockRecorder) ReadContractStorage(ctx, addr, key interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReadContractStorage(ctx, addr, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadContractStorage", reflect.TypeOf((*MockCoreService)(nil).ReadContractStorage), ctx, addr, key) } @@ -606,7 +611,7 @@ func (m *MockCoreService) ReadContractStorageAt(ctx context.Context, addr addres } // ReadContractStorageAt indicates an expected call of ReadContractStorageAt. -func (mr *MockCoreServiceMockRecorder) ReadContractStorageAt(ctx, addr, key, height interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReadContractStorageAt(ctx, addr, key, height any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadContractStorageAt", reflect.TypeOf((*MockCoreService)(nil).ReadContractStorageAt), ctx, addr, key, height) } @@ -621,7 +626,7 @@ func (m *MockCoreService) ReadState(protocolID, height string, methodName []byte } // ReadState indicates an expected call of ReadState. -func (mr *MockCoreServiceMockRecorder) ReadState(protocolID, height, methodName, arguments interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReadState(protocolID, height, methodName, arguments any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadState", reflect.TypeOf((*MockCoreService)(nil).ReadState), protocolID, height, methodName, arguments) } @@ -636,7 +641,7 @@ func (m *MockCoreService) ReceiptByActionHash(h hash.Hash256) (*action.Receipt, } // ReceiptByActionHash indicates an expected call of ReceiptByActionHash. -func (mr *MockCoreServiceMockRecorder) ReceiptByActionHash(h interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReceiptByActionHash(h any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiptByActionHash", reflect.TypeOf((*MockCoreService)(nil).ReceiptByActionHash), h) } @@ -650,7 +655,7 @@ func (m *MockCoreService) ReceiveBlock(blk *block.Block) error { } // ReceiveBlock indicates an expected call of ReceiveBlock. -func (mr *MockCoreServiceMockRecorder) ReceiveBlock(blk interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) ReceiveBlock(blk any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveBlock", reflect.TypeOf((*MockCoreService)(nil).ReceiveBlock), blk) } @@ -665,7 +670,7 @@ func (m *MockCoreService) SendAction(ctx context.Context, in *iotextypes.Action) } // SendAction indicates an expected call of SendAction. -func (mr *MockCoreServiceMockRecorder) SendAction(ctx, in interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) SendAction(ctx, in any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAction", reflect.TypeOf((*MockCoreService)(nil).SendAction), ctx, in) } @@ -699,7 +704,7 @@ func (m *MockCoreService) SimulateExecution(arg0 context.Context, arg1 address.A } // SimulateExecution indicates an expected call of SimulateExecution. -func (mr *MockCoreServiceMockRecorder) SimulateExecution(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) SimulateExecution(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimulateExecution", reflect.TypeOf((*MockCoreService)(nil).SimulateExecution), arg0, arg1, arg2) } @@ -713,7 +718,7 @@ func (m *MockCoreService) Start(ctx context.Context) error { } // Start indicates an expected call of Start. -func (mr *MockCoreServiceMockRecorder) Start(ctx interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Start(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockCoreService)(nil).Start), ctx) } @@ -727,7 +732,7 @@ func (m *MockCoreService) Stop(ctx context.Context) error { } // Stop indicates an expected call of Stop. -func (mr *MockCoreServiceMockRecorder) Stop(ctx interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Stop(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockCoreService)(nil).Stop), ctx) } @@ -804,7 +809,7 @@ func (m *MockCoreService) TraceBlockByHash(ctx context.Context, blkHash string, } // TraceBlockByHash indicates an expected call of TraceBlockByHash. -func (mr *MockCoreServiceMockRecorder) TraceBlockByHash(ctx, blkHash, config interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TraceBlockByHash(ctx, blkHash, config any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceBlockByHash", reflect.TypeOf((*MockCoreService)(nil).TraceBlockByHash), ctx, blkHash, config) } @@ -821,7 +826,7 @@ func (m *MockCoreService) TraceBlockByNumber(ctx context.Context, height uint64, } // TraceBlockByNumber indicates an expected call of TraceBlockByNumber. -func (mr *MockCoreServiceMockRecorder) TraceBlockByNumber(ctx, height, config interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TraceBlockByNumber(ctx, height, config any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceBlockByNumber", reflect.TypeOf((*MockCoreService)(nil).TraceBlockByNumber), ctx, height, config) } @@ -838,7 +843,7 @@ func (m *MockCoreService) TraceCall(ctx context.Context, callerAddr address.Addr } // TraceCall indicates an expected call of TraceCall. -func (mr *MockCoreServiceMockRecorder) TraceCall(ctx, callerAddr, height, contractAddress, nonce, amount, gasLimit, data, config interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TraceCall(ctx, callerAddr, height, contractAddress, nonce, amount, gasLimit, data, config any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceCall", reflect.TypeOf((*MockCoreService)(nil).TraceCall), ctx, callerAddr, height, contractAddress, nonce, amount, gasLimit, data, config) } @@ -855,7 +860,7 @@ func (m *MockCoreService) TraceTransaction(ctx context.Context, actHash string, } // TraceTransaction indicates an expected call of TraceTransaction. -func (mr *MockCoreServiceMockRecorder) TraceTransaction(ctx, actHash, config interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TraceTransaction(ctx, actHash, config any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceTransaction", reflect.TypeOf((*MockCoreService)(nil).TraceTransaction), ctx, actHash, config) } @@ -867,7 +872,7 @@ func (m *MockCoreService) Track(ctx context.Context, start time.Time, method str } // Track indicates an expected call of Track. -func (mr *MockCoreServiceMockRecorder) Track(ctx, start, method, size, success interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) Track(ctx, start, method, size, success any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Track", reflect.TypeOf((*MockCoreService)(nil).Track), ctx, start, method, size, success) } @@ -882,7 +887,7 @@ func (m *MockCoreService) TransactionLogByActionHash(actHash string) (*iotextype } // TransactionLogByActionHash indicates an expected call of TransactionLogByActionHash. -func (mr *MockCoreServiceMockRecorder) TransactionLogByActionHash(actHash interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TransactionLogByActionHash(actHash any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionLogByActionHash", reflect.TypeOf((*MockCoreService)(nil).TransactionLogByActionHash), actHash) } @@ -898,24 +903,24 @@ func (m *MockCoreService) TransactionLogByBlockHeight(blockHeight uint64) (*iote } // TransactionLogByBlockHeight indicates an expected call of TransactionLogByBlockHeight. -func (mr *MockCoreServiceMockRecorder) TransactionLogByBlockHeight(blockHeight interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) TransactionLogByBlockHeight(blockHeight any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionLogByBlockHeight", reflect.TypeOf((*MockCoreService)(nil).TransactionLogByBlockHeight), blockHeight) } // UnconfirmedActionsByAddress mocks base method. -func (m *MockCoreService) UnconfirmedActionsByAddress(address string, start, count uint64) ([]*iotexapi.ActionInfo, error) { +func (m *MockCoreService) UnconfirmedActionsByAddress(arg0 string, start, count uint64) ([]*iotexapi.ActionInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UnconfirmedActionsByAddress", address, start, count) + ret := m.ctrl.Call(m, "UnconfirmedActionsByAddress", arg0, start, count) ret0, _ := ret[0].([]*iotexapi.ActionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // UnconfirmedActionsByAddress indicates an expected call of UnconfirmedActionsByAddress. -func (mr *MockCoreServiceMockRecorder) UnconfirmedActionsByAddress(address, start, count interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) UnconfirmedActionsByAddress(arg0, start, count any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnconfirmedActionsByAddress", reflect.TypeOf((*MockCoreService)(nil).UnconfirmedActionsByAddress), address, start, count) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnconfirmedActionsByAddress", reflect.TypeOf((*MockCoreService)(nil).UnconfirmedActionsByAddress), arg0, start, count) } // WithHeight mocks base method. @@ -927,7 +932,7 @@ func (m *MockCoreService) WithHeight(arg0 uint64) CoreServiceReaderWithHeight { } // WithHeight indicates an expected call of WithHeight. -func (mr *MockCoreServiceMockRecorder) WithHeight(arg0 interface{}) *gomock.Call { +func (mr *MockCoreServiceMockRecorder) WithHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithHeight", reflect.TypeOf((*MockCoreService)(nil).WithHeight), arg0) } @@ -936,6 +941,7 @@ func (mr *MockCoreServiceMockRecorder) WithHeight(arg0 interface{}) *gomock.Call type MockintrinsicGasCalculator struct { ctrl *gomock.Controller recorder *MockintrinsicGasCalculatorMockRecorder + isgomock struct{} } // MockintrinsicGasCalculatorMockRecorder is the mock recorder for MockintrinsicGasCalculator. diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index 88a04cd0c3..cddc22c4d3 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -11,6 +11,9 @@ import ( "github.com/pkg/errors" + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -49,6 +52,63 @@ func newContractStakingDirty(clean stakingCache) *contractStakingDirty { } } +func (dirty *contractStakingDirty) PutBucketType(contractAddr address.Address, bt *BucketType) error { + dirty.putBucketType(bt) + return nil +} + +func (dirty *contractStakingDirty) DeductBucket(contractAddr address.Address, id uint64) (*contractstaking.Bucket, error) { + bi, ok := dirty.cache.BucketInfo(id) + if !ok { + return nil, errors.Wrapf(contractstaking.ErrBucketNotExist, "bucket info %d not found", id) + } + bt, ok := dirty.cache.BucketType(bi.TypeIndex) + if !ok { + return nil, errors.New("bucket type not found") + } + return &contractstaking.Bucket{ + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: bi.CreatedAt, + UnlockedAt: bi.UnlockedAt, + UnstakedAt: bi.UnstakedAt, + Candidate: bi.Delegate, + Owner: bi.Owner, + }, nil +} + +func (dirty *contractStakingDirty) DeleteBucket(contractAddr address.Address, id uint64) error { + dirty.deleteBucketInfo(id) + return nil +} + +func (dirty *contractStakingDirty) PutBucket(contractAddr address.Address, id uint64, bkt *contractstaking.Bucket) error { + bi, err := dirty.convertToBucketInfo(bkt) + if err != nil { + return err + } + dirty.addBucketInfo(id, bi) + return nil +} + +func (dirty *contractStakingDirty) convertToBucketInfo(bucket *contractstaking.Bucket) (*bucketInfo, error) { + if bucket == nil { + return nil, nil + } + tid, old := dirty.matchBucketType(bucket.StakedAmount, bucket.StakedDuration) + if old == nil { + return nil, errBucketTypeNotExist + } + return &bucketInfo{ + TypeIndex: tid, + CreatedAt: bucket.CreatedAt, + UnlockedAt: bucket.UnlockedAt, + UnstakedAt: bucket.UnstakedAt, + Delegate: bucket.Candidate, + Owner: bucket.Owner, + }, nil +} + func (dirty *contractStakingDirty) addBucketInfo(id uint64, bi *bucketInfo) { data, err := bi.Serialize() if err != nil { @@ -89,7 +149,7 @@ func (dirty *contractStakingDirty) getBucketInfo(id uint64) (*bucketInfo, bool) return dirty.cache.BucketInfo(id) } -func (dirty *contractStakingDirty) finalize() (batch.KVStoreBatch, stakingCache) { +func (dirty *contractStakingDirty) Finalize() (batch.KVStoreBatch, stakingCache) { b := dirty.finalizeBatch() return b, dirty.cache diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index 9d6736bb37..f2621f4cf1 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -147,7 +147,7 @@ func TestContractStakingDirty_finalize(t *testing.T) { require.EqualValues(0, totalCnt) // no dirty data - batcher, cache := dirty.finalize() + batcher, cache := dirty.Finalize() require.EqualValues(1, batcher.Size()) info, err := batcher.Entry(0) require.NoError(err) @@ -160,7 +160,7 @@ func TestContractStakingDirty_finalize(t *testing.T) { // added bucket type bt := &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1} dirty.addBucketType(1, bt) - batcher, cache = dirty.finalize() + batcher, cache = dirty.Finalize() require.EqualValues(2, batcher.Size()) info, err = batcher.Entry(1) require.NoError(err) @@ -175,7 +175,7 @@ func TestContractStakingDirty_finalize(t *testing.T) { // add bucket info bi := &bucketInfo{TypeIndex: 1, CreatedAt: 2, UnlockedAt: 3, UnstakedAt: 4, Delegate: identityset.Address(1), Owner: identityset.Address(2)} dirty.addBucketInfo(1, bi) - batcher, cache = dirty.finalize() + batcher, cache = dirty.Finalize() require.EqualValues(3, batcher.Size()) info, err = batcher.Entry(2) require.NoError(err) diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/eventprocessor.go similarity index 63% rename from blockindex/contractstaking/event_handler.go rename to blockindex/contractstaking/eventprocessor.go index abcea1db29..6d5981ec14 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/eventprocessor.go @@ -17,7 +17,8 @@ import ( "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/iotexproject/iotex-core/v2/action" - "github.com/iotexproject/iotex-core/v2/db/batch" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) const ( @@ -348,10 +349,11 @@ const ( ]` ) -// contractStakingEventHandler handles events from staking contract -type contractStakingEventHandler struct { - dirty *contractStakingDirty - tokenOwner map[uint64]address.Address +// contractStakingEventProcessor handles events from staking contract +type contractStakingEventProcessor struct { + dirty staking.EventHandler + contractAddr address.Address + tokenOwner map[uint64]address.Address } var ( @@ -366,24 +368,25 @@ func init() { } } -func newContractStakingEventHandler(cache stakingCache) *contractStakingEventHandler { - dirty := newContractStakingDirty(cache) - return &contractStakingEventHandler{ - dirty: dirty, - tokenOwner: make(map[uint64]address.Address), +func newContractStakingEventProcessor(contractAddr address.Address, dirty staking.EventHandler) *contractStakingEventProcessor { + return &contractStakingEventProcessor{ + dirty: dirty, + contractAddr: contractAddr, + tokenOwner: make(map[uint64]address.Address), } } -func (eh *contractStakingEventHandler) HandleReceipts(ctx context.Context, height uint64, receipts []*action.Receipt, contractAddr string) error { +func (processor *contractStakingEventProcessor) ProcessReceipts(ctx context.Context, receipts ...*action.Receipt) error { + expectedContractAddr := processor.contractAddr.String() for _, receipt := range receipts { if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { continue } for _, log := range receipt.Logs() { - if log.Address != contractAddr { + if log.Address != expectedContractAddr { continue } - if err := eh.HandleEvent(ctx, height, log); err != nil { + if err := processor.HandleEvent(ctx, log); err != nil { return err } } @@ -391,7 +394,7 @@ func (eh *contractStakingEventHandler) HandleReceipts(ctx context.Context, heigh return nil } -func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, height uint64, log *action.Log) error { +func (processor *contractStakingEventProcessor) HandleEvent(ctx context.Context, log *action.Log) error { // get event abi abiEvent, err := _stakingInterface.EventByID(common.Hash(log.Topics[0])) if err != nil { @@ -407,27 +410,27 @@ func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, height u // handle different kinds of event switch abiEvent.Name { case "BucketTypeActivated": - return eh.handleBucketTypeActivatedEvent(event, height) + return processor.handleBucketTypeActivatedEvent(event, log.BlockHeight) case "BucketTypeDeactivated": - return eh.handleBucketTypeDeactivatedEvent(event, height) + return processor.handleBucketTypeDeactivatedEvent(event, log.BlockHeight) case "Staked": - return eh.handleStakedEvent(event, height) + return processor.handleStakedEvent(event, log.BlockHeight) case "Locked": - return eh.handleLockedEvent(event) + return processor.handleLockedEvent(event) case "Unlocked": - return eh.handleUnlockedEvent(event, height) + return processor.handleUnlockedEvent(event, log.BlockHeight) case "Unstaked": - return eh.handleUnstakedEvent(event, height) + return processor.handleUnstakedEvent(event, log.BlockHeight) case "Merged": - return eh.handleMergedEvent(event) + return processor.handleMergedEvent(event) case "BucketExpanded": - return eh.handleBucketExpandedEvent(event) + return processor.handleBucketExpandedEvent(event) case "DelegateChanged": - return eh.handleDelegateChangedEvent(event) + return processor.handleDelegateChangedEvent(event) case "Withdrawal": - return eh.handleWithdrawalEvent(event) + return processor.handleWithdrawalEvent(event) case "Transfer": - return eh.handleTransferEvent(event) + return processor.handleTransferEvent(event) case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused": // not require handling events return nil @@ -436,11 +439,7 @@ func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, height u } } -func (eh *contractStakingEventHandler) Result() (batch.KVStoreBatch, stakingCache) { - return eh.dirty.finalize() -} - -func (eh *contractStakingEventHandler) handleTransferEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleTransferEvent(event eventParam) error { to, err := event.IndexedFieldAddress("to") if err != nil { return err @@ -452,17 +451,21 @@ func (eh *contractStakingEventHandler) handleTransferEvent(event eventParam) err tokenID := tokenIDParam.Uint64() // cache token owner for stake event - eh.tokenOwner[tokenID] = to + processor.tokenOwner[tokenID] = to // update bucket owner if token exists - if bi, ok := eh.dirty.getBucketInfo(tokenID); ok { - bi.Owner = to - eh.dirty.updateBucketInfo(tokenID, bi) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenID) + switch errors.Cause(err) { + case nil: + bucket.Owner = to + return processor.dirty.PutBucket(processor.contractAddr, tokenID, bucket) + case contractstaking.ErrBucketNotExist: + return nil + default: + return err } - - return nil } -func (eh *contractStakingEventHandler) handleBucketTypeActivatedEvent(event eventParam, height uint64) error { +func (processor *contractStakingEventProcessor) handleBucketTypeActivatedEvent(event eventParam, height uint64) error { amountParam, err := event.FieldUint256("amount") if err != nil { return err @@ -477,11 +480,10 @@ func (eh *contractStakingEventHandler) handleBucketTypeActivatedEvent(event even Duration: durationParam.Uint64(), ActivatedAt: height, } - eh.dirty.putBucketType(&bt) - return nil + return processor.dirty.PutBucketType(processor.contractAddr, &bt) } -func (eh *contractStakingEventHandler) handleBucketTypeDeactivatedEvent(event eventParam, height uint64) error { +func (processor *contractStakingEventProcessor) handleBucketTypeDeactivatedEvent(event eventParam, height uint64) error { amountParam, err := event.FieldUint256("amount") if err != nil { return err @@ -491,17 +493,16 @@ func (eh *contractStakingEventHandler) handleBucketTypeDeactivatedEvent(event ev return err } - id, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if bt == nil { - return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) + bt := BucketType{ + Amount: amountParam, + Duration: durationParam.Uint64(), + ActivatedAt: maxBlockNumber, } - bt.ActivatedAt = maxBlockNumber - eh.dirty.updateBucketType(id, bt) - return nil + return processor.dirty.PutBucketType(processor.contractAddr, &bt) } -func (eh *contractStakingEventHandler) handleStakedEvent(event eventParam, height uint64) error { +func (processor *contractStakingEventProcessor) handleStakedEvent(event eventParam, height uint64) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err @@ -519,27 +520,23 @@ func (eh *contractStakingEventHandler) handleStakedEvent(event eventParam, heigh return err } - btIdx, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if bt == nil { - return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) - } - owner, ok := eh.tokenOwner[tokenIDParam.Uint64()] + owner, ok := processor.tokenOwner[tokenIDParam.Uint64()] if !ok { return errors.Errorf("no owner for token id %d", tokenIDParam.Uint64()) } - bucket := bucketInfo{ - TypeIndex: btIdx, - Delegate: delegateParam, - Owner: owner, - CreatedAt: height, - UnlockedAt: maxBlockNumber, - UnstakedAt: maxBlockNumber, - } - eh.dirty.addBucketInfo(tokenIDParam.Uint64(), &bucket) - return nil + + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), &contractstaking.Bucket{ + Candidate: delegateParam, + Owner: owner, + StakedAmount: amountParam, + StakedDuration: durationParam.Uint64(), + CreatedAt: height, + UnstakedAt: maxBlockNumber, + UnlockedAt: maxBlockNumber, + }) } -func (eh *contractStakingEventHandler) handleLockedEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleLockedEvent(event eventParam) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err @@ -549,58 +546,46 @@ func (eh *contractStakingEventHandler) handleLockedEvent(event eventParam) error return err } - b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) - if !ok { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) - } - bt, ok := eh.dirty.getBucketType(b.TypeIndex) - if !ok { - return errors.Wrapf(errBucketTypeNotExist, "id %d", b.TypeIndex) - } - newBtIdx, newBt := eh.dirty.matchBucketType(bt.Amount, durationParam.Uint64()) - if newBt == nil { - return errors.Wrapf(errBucketTypeNotExist, "amount %v, duration %d", bt.Amount, durationParam.Uint64()) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err } - b.TypeIndex = newBtIdx - b.UnlockedAt = maxBlockNumber - eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) - return nil + bucket.StakedDuration = durationParam.Uint64() + bucket.UnlockedAt = maxBlockNumber + + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleUnlockedEvent(event eventParam, height uint64) error { +func (processor *contractStakingEventProcessor) handleUnlockedEvent(event eventParam, height uint64) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err } - b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) - if !ok { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err } - b.UnlockedAt = height - eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) - - return nil + bucket.UnlockedAt = height + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleUnstakedEvent(event eventParam, height uint64) error { +func (processor *contractStakingEventProcessor) handleUnstakedEvent(event eventParam, height uint64) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err } - b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) - if !ok { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err } - b.UnstakedAt = height - eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) - - return nil + bucket.UnstakedAt = height + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleMergedEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleMergedEvent(event eventParam) error { tokenIDsParam, err := event.FieldUint256Slice("tokenIds") if err != nil { return err @@ -615,25 +600,22 @@ func (eh *contractStakingEventHandler) handleMergedEvent(event eventParam) error } // merge to the first bucket - btIdx, bt := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if bt == nil { - return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) - } - b, ok := eh.dirty.getBucketInfo(tokenIDsParam[0].Uint64()) - if !ok { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDsParam[0].Uint64()) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDsParam[0].Uint64()) + if err != nil { + return err } - b.TypeIndex = btIdx - b.UnlockedAt = maxBlockNumber + bucket.StakedAmount = amountParam + bucket.StakedDuration = durationParam.Uint64() + bucket.UnlockedAt = maxBlockNumber for i := 1; i < len(tokenIDsParam); i++ { - eh.dirty.deleteBucketInfo(tokenIDsParam[i].Uint64()) + if err := processor.dirty.DeleteBucket(processor.contractAddr, tokenIDsParam[i].Uint64()); err != nil { + return err + } } - eh.dirty.updateBucketInfo(tokenIDsParam[0].Uint64(), b) - - return nil + return processor.dirty.PutBucket(processor.contractAddr, tokenIDsParam[0].Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleBucketExpandedEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleBucketExpandedEvent(event eventParam) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err @@ -647,21 +629,17 @@ func (eh *contractStakingEventHandler) handleBucketExpandedEvent(event eventPara return err } - b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) - if !ok { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) - } - newBtIdx, newBucketType := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) - if newBucketType == nil { - return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err } - b.TypeIndex = newBtIdx - eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) + bucket.StakedAmount = amountParam + bucket.StakedDuration = durationParam.Uint64() - return nil + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleDelegateChangedEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleDelegateChangedEvent(event eventParam) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err @@ -671,22 +649,19 @@ func (eh *contractStakingEventHandler) handleDelegateChangedEvent(event eventPar return err } - b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) - if !ok { + bucket, err := processor.dirty.DeductBucket(processor.contractAddr, tokenIDParam.Uint64()) + if err != nil { return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) } - b.Delegate = delegateParam - eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) + bucket.Candidate = delegateParam - return nil + return processor.dirty.PutBucket(processor.contractAddr, tokenIDParam.Uint64(), bucket) } -func (eh *contractStakingEventHandler) handleWithdrawalEvent(event eventParam) error { +func (processor *contractStakingEventProcessor) handleWithdrawalEvent(event eventParam) error { tokenIDParam, err := event.IndexedFieldUint256("tokenId") if err != nil { return err } - eh.dirty.deleteBucketInfo(tokenIDParam.Uint64()) - - return nil + return processor.dirty.DeleteBucket(processor.contractAddr, tokenIDParam.Uint64()) } diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 7e21fe5bf2..b6fe9c2616 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -85,6 +85,14 @@ func (s *Indexer) Start(ctx context.Context) error { return s.start(ctx) } +// CreateEventProcessor creates a new event processor for contract staking +func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.EventHandler) staking.EventProcessor { + return newContractStakingEventProcessor( + s.contractAddr, + handler, + ) +} + // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { if !s.IsReady() { @@ -382,8 +390,9 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { if blk.Height() > expectHeight { return errors.Errorf("invalid block height %d, expect %d", blk.Height(), expectHeight) } - handler := newContractStakingEventHandler(cache) - if err := handler.HandleReceipts(ctx, blk.Height(), blk.Receipts, s.contractAddr.String()); err != nil { + handler := newContractStakingDirty(cache) + processor := newContractStakingEventProcessor(s.contractAddr, handler) + if err := processor.ProcessReceipts(ctx, blk.Receipts...); err != nil { return errors.Wrapf(err, "failed to handle receipts at height %d", blk.Height()) } s.mu.Lock() @@ -395,8 +404,8 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { return nil } -func (s *Indexer) commit(ctx context.Context, handler *contractStakingEventHandler, height uint64) error { - batch, delta := handler.Result() +func (s *Indexer) commit(ctx context.Context, handler *contractStakingDirty, height uint64) error { + batch, delta := handler.Finalize() cache, err := delta.Commit(ctx, s.contractAddr, nil) if err != nil { return errors.Wrapf(err, "failed to commit delta") diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index fcad0fa702..6725b7e23a 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -103,12 +103,15 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { // create a stake height := uint64(1) startHeight := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) activateBucketType(r, handler, 10, 100, height) owner := identityset.Address(0) delegate := identityset.Address(1) stake(r, handler, owner, delegate, 1, 10, 100, height) - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) buckets, err := indexer.Buckets(height) r.NoError(err) @@ -168,12 +171,12 @@ func TestContractStakingIndexerDirty(t *testing.T) { // before commit dirty, the cache should be empty height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) gotHeight, err := indexer.Height() r.NoError(err) r.EqualValues(0, gotHeight) // after commit dirty, the cache should be updated - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) gotHeight, err = indexer.Height() r.NoError(err) @@ -198,6 +201,8 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) wait := sync.WaitGroup{} wait.Add(6) @@ -228,16 +233,17 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { defer wait.Done() // activate bucket type indexer.mu.Lock() - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) activateBucketType(r, handler, 10, 100, 1) - r.NoError(indexer.commit(context.Background(), handler, 1)) + r.NoError(indexer.commit(context.Background(), dirty, 1)) indexer.mu.Unlock() for i := 2; i < 1000; i++ { height := uint64(i) indexer.mu.Lock() - handler := newContractStakingEventHandler(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) stake(r, handler, owner, delegate, int64(i), 10, 100, height) - err := indexer.commit(context.Background(), handler, height) + err := indexer.commit(context.Background(), dirty, height) r.NoError(err) indexer.mu.Unlock() } @@ -263,6 +269,8 @@ func TestContractStakingIndexerBucketType(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) // activate bucketTypeData := [][2]int64{ @@ -282,11 +290,12 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) bucketTypes, err := indexer.BucketTypes(height) r.NoError(err) @@ -297,12 +306,12 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } // deactivate height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) for i := 0; i < 2; i++ { data := bucketTypeData[i] deactivateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -313,12 +322,12 @@ func TestContractStakingIndexerBucketType(t *testing.T) { } // reactivate height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) for i := 0; i < 2; i++ { data := bucketTypeData[i] activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -351,6 +360,8 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) // init bucket type bucketTypeData := [][2]int64{ @@ -360,11 +371,12 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { {20, 100}, } height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.TestDefault()), protocol.BlockCtx{BlockHeight: 1})) @@ -373,10 +385,10 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { delegate := identityset.Address(1) height++ createHeight := height - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) @@ -400,9 +412,9 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { // transfer newOwner := identityset.Address(2) height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) transfer(r, handler, newOwner, int64(bucket.Index)) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -410,9 +422,9 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { // unlock height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) unlock(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -434,9 +446,9 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { // lock again height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) lock(r, handler, int64(bucket.Index), int64(10)) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -458,11 +470,11 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { // unstake height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) unlock(r, handler, int64(bucket.Index), height) t.Log("unstake bucket", bucket.Index, "at height", height) unstake(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -484,9 +496,9 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { // withdraw height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) withdraw(r, handler, int64(bucket.Index)) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.False(ok) @@ -514,6 +526,8 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) // init bucket type bucketTypeData := [][2]int64{ @@ -523,27 +537,28 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { {20, 100}, } height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) t.Run("expand bucket type", func(t *testing.T) { owner := identityset.Address(0) delegate := identityset.Address(1) height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) expandBucketType(r, handler, int64(bucket.Index), 20, 100) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -568,6 +583,8 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) // init bucket type bucketTypeData := [][2]int64{ @@ -577,11 +594,12 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { {20, 100}, } height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(context.Background(), handler, height) + err = indexer.commit(context.Background(), dirty, height) r.NoError(err) // stake @@ -597,12 +615,12 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { {1, 3, 20, 100}, } height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) for i, data := range stakeData { stake(r, handler, identityset.Address(data.owner), identityset.Address(data.delegate), int64(i+1), int64(data.amount), int64(data.duration), height) } r.NoError(err) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) t.Run("Buckets", func(t *testing.T) { buckets, err := indexer.Buckets(height) @@ -673,10 +691,13 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) // init bucket type height := uint64(1) - handler := newContractStakingEventHandler(newWrappedCache(indexer.cache)) + dirty := newContractStakingDirty(newWrappedCache(indexer.cache)) + handler := newContractStakingEventProcessor(contractAddr, dirty) activateBucketType(r, handler, 10, 10, height) activateBucketType(r, handler, 20, 20, height) // create bucket @@ -693,7 +714,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.Len(ids, 0) r.Len(bts, 0) r.Len(bis, 0) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) abt = indexer.cache.ActiveBucketTypes() r.Len(abt, 2) ids, bts, bis = indexer.cache.Buckets() @@ -702,7 +723,8 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.Len(bis, 4) height++ - handler = newContractStakingEventHandler(newWrappedCache(indexer.cache)) + dirty = newContractStakingDirty(newWrappedCache(indexer.cache)) + handler = newContractStakingEventProcessor(contractAddr, dirty) changeDelegate(r, handler, delegate1, 3) transfer(r, handler, delegate1, 1) bt, ok, err := indexer.Bucket(3, height-1) @@ -713,7 +735,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.NoError(err) r.True(ok) r.Equal(owner.String(), bt.Owner.String()) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) bt, ok, err = indexer.Bucket(3, height) r.NoError(err) r.True(ok) @@ -740,11 +762,14 @@ func TestContractStakingIndexerVotes(t *testing.T) { }) r.NoError(err) r.NoError(indexer.Start(context.Background())) + contractAddr, err := address.FromString(_testStakingContractAddress) + r.NoError(err) ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.TestDefault()), protocol.BlockCtx{BlockHeight: 1})) // init bucket type height := uint64(1) - handler := newContractStakingEventHandler(indexer.cache) + dirty := newContractStakingDirty(indexer.cache) + handler := newContractStakingEventProcessor(contractAddr, dirty) activateBucketType(r, handler, 10, 10, height) activateBucketType(r, handler, 20, 20, height) activateBucketType(r, handler, 30, 20, height) @@ -757,7 +782,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate1, 2, 20, 20, height) stake(r, handler, owner, delegate2, 3, 20, 20, height) stake(r, handler, owner, delegate2, 4, 20, 20, height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err := indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(30, votes.Uint64()) @@ -769,9 +794,9 @@ func TestContractStakingIndexerVotes(t *testing.T) { // change delegate bucket 3 to delegate1 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) changeDelegate(r, handler, delegate1, 3) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -781,10 +806,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { // unlock bucket 1 & 4 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) unlock(r, handler, 1, height) unlock(r, handler, 4, height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -794,10 +819,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { // unstake bucket 1 & lock 4 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) unstake(r, handler, 1, height) lock(r, handler, 4, 20) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(uint64(40), votes.Uint64()) @@ -807,9 +832,9 @@ func TestContractStakingIndexerVotes(t *testing.T) { // expand bucket 2 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) expandBucketType(r, handler, 2, 30, 20) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -819,9 +844,9 @@ func TestContractStakingIndexerVotes(t *testing.T) { // transfer bucket 4 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) transfer(r, handler, delegate2, 4) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -831,11 +856,11 @@ func TestContractStakingIndexerVotes(t *testing.T) { // create bucket 5, 6, 7 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) stake(r, handler, owner, delegate2, 5, 20, 20, height) stake(r, handler, owner, delegate2, 6, 20, 20, height) stake(r, handler, owner, delegate2, 7, 20, 20, height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -845,9 +870,9 @@ func TestContractStakingIndexerVotes(t *testing.T) { // merge bucket 5, 6, 7 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -857,10 +882,10 @@ func TestContractStakingIndexerVotes(t *testing.T) { // unlock & unstake 5 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) unlock(r, handler, 5, height) unstake(r, handler, 5, height) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -870,12 +895,12 @@ func TestContractStakingIndexerVotes(t *testing.T) { // create & merge bucket 8, 9, 10 height++ - handler = newContractStakingEventHandler(indexer.cache) + handler = newContractStakingEventProcessor(contractAddr, dirty) stake(r, handler, owner, delegate1, 8, 20, 20, height) stake(r, handler, owner, delegate2, 9, 20, 20, height) stake(r, handler, owner, delegate2, 10, 20, 20, height) mergeBuckets(r, handler, []int64{8, 9, 10}, 60, 20) - r.NoError(indexer.commit(context.Background(), handler, height)) + r.NoError(indexer.commit(context.Background(), dirty, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(110, votes.Uint64()) @@ -1180,7 +1205,7 @@ func BenchmarkIndexer_PutBlockBeforeContractHeight(b *testing.B) { } } -func activateBucketType(r *require.Assertions, handler *contractStakingEventHandler, amount, duration int64, height uint64) { +func activateBucketType(r *require.Assertions, handler *contractStakingEventProcessor, amount, duration int64, height uint64) { err := handler.handleBucketTypeActivatedEvent(eventParam{ "amount": big.NewInt(amount), "duration": big.NewInt(duration), @@ -1188,7 +1213,7 @@ func activateBucketType(r *require.Assertions, handler *contractStakingEventHand r.NoError(err) } -func deactivateBucketType(r *require.Assertions, handler *contractStakingEventHandler, amount, duration int64, height uint64) { +func deactivateBucketType(r *require.Assertions, handler *contractStakingEventProcessor, amount, duration int64, height uint64) { err := handler.handleBucketTypeDeactivatedEvent(eventParam{ "amount": big.NewInt(amount), "duration": big.NewInt(duration), @@ -1196,7 +1221,7 @@ func deactivateBucketType(r *require.Assertions, handler *contractStakingEventHa r.NoError(err) } -func stake(r *require.Assertions, handler *contractStakingEventHandler, owner, candidate address.Address, token, amount, duration int64, height uint64) { +func stake(r *require.Assertions, handler *contractStakingEventProcessor, owner, candidate address.Address, token, amount, duration int64, height uint64) { err := handler.handleTransferEvent(eventParam{ "to": common.BytesToAddress(owner.Bytes()), "tokenId": big.NewInt(token), @@ -1211,14 +1236,14 @@ func stake(r *require.Assertions, handler *contractStakingEventHandler, owner, c r.NoError(err) } -func unlock(r *require.Assertions, handler *contractStakingEventHandler, token int64, height uint64) { +func unlock(r *require.Assertions, handler *contractStakingEventProcessor, token int64, height uint64) { err := handler.handleUnlockedEvent(eventParam{ "tokenId": big.NewInt(token), }, height) r.NoError(err) } -func lock(r *require.Assertions, handler *contractStakingEventHandler, token, duration int64) { +func lock(r *require.Assertions, handler *contractStakingEventProcessor, token, duration int64) { err := handler.handleLockedEvent(eventParam{ "tokenId": big.NewInt(token), "duration": big.NewInt(duration), @@ -1226,21 +1251,21 @@ func lock(r *require.Assertions, handler *contractStakingEventHandler, token, du r.NoError(err) } -func unstake(r *require.Assertions, handler *contractStakingEventHandler, token int64, height uint64) { +func unstake(r *require.Assertions, handler *contractStakingEventProcessor, token int64, height uint64) { err := handler.handleUnstakedEvent(eventParam{ "tokenId": big.NewInt(token), }, height) r.NoError(err) } -func withdraw(r *require.Assertions, handler *contractStakingEventHandler, token int64) { +func withdraw(r *require.Assertions, handler *contractStakingEventProcessor, token int64) { err := handler.handleWithdrawalEvent(eventParam{ "tokenId": big.NewInt(token), }) r.NoError(err) } -func expandBucketType(r *require.Assertions, handler *contractStakingEventHandler, token, amount, duration int64) { +func expandBucketType(r *require.Assertions, handler *contractStakingEventProcessor, token, amount, duration int64) { err := handler.handleBucketExpandedEvent(eventParam{ "tokenId": big.NewInt(token), "amount": big.NewInt(amount), @@ -1249,7 +1274,7 @@ func expandBucketType(r *require.Assertions, handler *contractStakingEventHandle r.NoError(err) } -func transfer(r *require.Assertions, handler *contractStakingEventHandler, owner address.Address, token int64) { +func transfer(r *require.Assertions, handler *contractStakingEventProcessor, owner address.Address, token int64) { err := handler.handleTransferEvent(eventParam{ "to": common.BytesToAddress(owner.Bytes()), "tokenId": big.NewInt(token), @@ -1257,7 +1282,7 @@ func transfer(r *require.Assertions, handler *contractStakingEventHandler, owner r.NoError(err) } -func changeDelegate(r *require.Assertions, handler *contractStakingEventHandler, delegate address.Address, token int64) { +func changeDelegate(r *require.Assertions, handler *contractStakingEventProcessor, delegate address.Address, token int64) { err := handler.handleDelegateChangedEvent(eventParam{ "newDelegate": common.BytesToAddress(delegate.Bytes()), "tokenId": big.NewInt(token), @@ -1265,7 +1290,7 @@ func changeDelegate(r *require.Assertions, handler *contractStakingEventHandler, r.NoError(err) } -func mergeBuckets(r *require.Assertions, handler *contractStakingEventHandler, tokenIds []int64, amount, duration int64) { +func mergeBuckets(r *require.Assertions, handler *contractStakingEventProcessor, tokenIds []int64, amount, duration int64) { tokens := make([]*big.Int, len(tokenIds)) for i, token := range tokenIds { tokens[i] = big.NewInt(token) diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index 6de2803567..22650faf08 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -5,7 +5,6 @@ import ( "slices" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action" @@ -106,23 +105,13 @@ func (s *stakeView) CreatePreStates(ctx context.Context) error { } func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { - blkCtx := protocol.MustGetBlockCtx(ctx) // new event handler for this receipt - handler := newContractStakingEventHandler(newWrappedCache(s.cache)) - - // handle events of receipt - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - return nil - } - for _, log := range receipt.Logs() { - if log.Address != s.contractAddr.String() { - continue - } - if err := handler.HandleEvent(ctx, blkCtx.BlockHeight, log); err != nil { - return err - } + handler := newContractStakingDirty(newWrappedCache(s.cache)) + processor := newContractStakingEventProcessor(s.contractAddr, handler) + if err := processor.ProcessReceipts(ctx, receipt); err != nil { + return err } - _, delta := handler.Result() + _, delta := handler.Finalize() s.cache = delta return nil @@ -151,11 +140,12 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec return errors.Errorf("invalid block height %d, expect %d", height, expectHeight) } - handler := newContractStakingEventHandler(newWrappedCache(s.cache)) - if err := handler.HandleReceipts(ctx, height, receipts, s.contractAddr.String()); err != nil { + handler := newContractStakingDirty(newWrappedCache(s.cache)) + processor := newContractStakingEventProcessor(s.contractAddr, handler) + if err := processor.ProcessReceipts(ctx, receipts...); err != nil { return err } - _, delta := handler.Result() + _, delta := handler.Finalize() s.cache = delta s.height = height return nil diff --git a/systemcontractindex/stakingindex/event_handler.go b/systemcontractindex/stakingindex/event_handler.go index 8b383b205a..abad132189 100644 --- a/systemcontractindex/stakingindex/event_handler.go +++ b/systemcontractindex/stakingindex/event_handler.go @@ -1,293 +1,40 @@ package stakingindex import ( - "context" - _ "embed" - "math" - "strings" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "go.uber.org/zap" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" - - "github.com/iotexproject/iotex-core/v2/action" - "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db/batch" - "github.com/iotexproject/iotex-core/v2/pkg/log" - "github.com/iotexproject/iotex-core/v2/pkg/util/abiutil" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) -const ( - maxStakingNumber uint64 = math.MaxUint64 -) - -var ( - //go:embed staking_contract_abi_v3.json - stakingContractABIJSON string - // StakingContractABI is the abi of staking contract - StakingContractABI abi.ABI - - // ErrBucketNotExist is the error when bucket does not exist - ErrBucketNotExist = errors.New("bucket does not exist") -) - -type eventHandler struct { - stakingBucketNS string - dirty indexerCache // dirty cache, a view for current block - delta batch.KVStoreBatch // delta for db to store buckets of current block - tokenOwner map[uint64]address.Address - // context for event handler - blockCtx protocol.BlockCtx - timestamped bool - muted bool -} - -func init() { - var err error - StakingContractABI, err = abi.JSON(strings.NewReader(stakingContractABIJSON)) - if err != nil { - panic(err) +type ( + eventHandler struct { + stakingBucketNS string + dirty indexerCache // dirty cache, a view for current block + delta batch.KVStoreBatch // delta for db to store buckets of current block } -} +) -func newEventHandler(bucketNS string, dirty indexerCache, blkCtx protocol.BlockCtx, timestamped, muted bool) *eventHandler { +func newEventHandler(bucketNS string, dirty indexerCache) *eventHandler { return &eventHandler{ stakingBucketNS: bucketNS, dirty: dirty, delta: batch.NewBatch(), - tokenOwner: make(map[uint64]address.Address), - blockCtx: blkCtx, - timestamped: timestamped, - muted: muted, } } -func (eh *eventHandler) HandleStakedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - delegateParam, err := event.FieldByIDAddress(1) - if err != nil { - return err - } - amountParam, err := event.FieldByIDUint256(2) - if err != nil { - return err - } - durationParam, err := event.FieldByIDUint256(3) - if err != nil { - return err - } - owner, ok := eh.tokenOwner[tokenIDParam.Uint64()] - if !ok { - return errors.Errorf("no owner for token id %d", tokenIDParam.Uint64()) - } - createdAt := eh.blockCtx.BlockHeight - if eh.timestamped { - createdAt = uint64(eh.blockCtx.BlockTimeStamp.Unix()) - } - bucket := &Bucket{ - Candidate: delegateParam, - Owner: owner, - StakedAmount: amountParam, - StakedDuration: durationParam.Uint64(), - CreatedAt: createdAt, - UnlockedAt: maxStakingNumber, - UnstakedAt: maxStakingNumber, - IsTimestampBased: eh.timestamped, - Muted: eh.muted, - } - eh.putBucket(tokenIDParam.Uint64(), bucket) - return nil +func (eh *eventHandler) PutBucketType(_ address.Address, bt *contractstaking.BucketType) error { + return errors.New("not implemented") } -func (eh *eventHandler) HandleLockedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - durationParam, err := event.FieldByIDUint256(1) - if err != nil { - return err - } - - bkt := eh.dirty.Bucket(tokenIDParam.Uint64()) +func (eh *eventHandler) DeductBucket(_ address.Address, id uint64) (*Bucket, error) { + bkt := eh.dirty.Bucket(id) if bkt == nil { - return errors.Errorf("no bucket for token id %d", tokenIDParam.Uint64()) - } - bkt.StakedDuration = durationParam.Uint64() - bkt.UnlockedAt = maxStakingNumber - bkt.Muted = eh.muted - eh.putBucket(tokenIDParam.Uint64(), bkt) - return nil -} - -func (eh *eventHandler) HandleUnlockedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - - bkt := eh.dirty.Bucket(tokenIDParam.Uint64()) - if bkt == nil { - return errors.Errorf("no bucket for token id %d", tokenIDParam.Uint64()) - } - bkt.UnlockedAt = eh.blockCtx.BlockHeight - if eh.timestamped { - bkt.UnlockedAt = uint64(eh.blockCtx.BlockTimeStamp.Unix()) - } - eh.putBucket(tokenIDParam.Uint64(), bkt) - return nil -} - -func (eh *eventHandler) HandleUnstakedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - - bkt := eh.dirty.Bucket(tokenIDParam.Uint64()) - if bkt == nil { - return errors.Errorf("no bucket for token id %d", tokenIDParam.Uint64()) - } - bkt.UnstakedAt = eh.blockCtx.BlockHeight - if eh.timestamped { - bkt.UnstakedAt = uint64(eh.blockCtx.BlockTimeStamp.Unix()) - } - eh.putBucket(tokenIDParam.Uint64(), bkt) - return nil -} - -func (eh *eventHandler) HandleDelegateChangedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - delegateParam, err := event.FieldByIDAddress(1) - if err != nil { - return err - } - - bkt := eh.dirty.Bucket(tokenIDParam.Uint64()) - if bkt == nil { - return errors.Errorf("no bucket for token id %d", tokenIDParam.Uint64()) - } - bkt.Candidate = delegateParam - eh.putBucket(tokenIDParam.Uint64(), bkt) - return nil -} - -func (eh *eventHandler) HandleWithdrawalEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - - eh.delBucket(tokenIDParam.Uint64()) - return nil -} - -func (eh *eventHandler) HandleTransferEvent(event *abiutil.EventParam) error { - to, err := event.FieldByIDAddress(1) - if err != nil { - return err - } - tokenIDParam, err := event.FieldByIDUint256(2) - if err != nil { - return err - } - - tokenID := tokenIDParam.Uint64() - // cache token owner for stake event - eh.tokenOwner[tokenID] = to - // update bucket owner if token exists - bkt := eh.dirty.Bucket(tokenID) - if bkt != nil { - bkt.Owner = to - eh.putBucket(tokenID, bkt) - } - return nil -} - -func (eh *eventHandler) HandleMergedEvent(event *abiutil.EventParam) error { - tokenIDsParam, err := event.FieldByIDUint256Slice(0) - if err != nil { - return err - } - amountParam, err := event.FieldByIDUint256(1) - if err != nil { - return err - } - durationParam, err := event.FieldByIDUint256(2) - if err != nil { - return err + return nil, errors.Wrapf(contractstaking.ErrBucketNotExist, "token id %d", id) } - - // merge to the first bucket - b := eh.dirty.Bucket(tokenIDsParam[0].Uint64()) - if b == nil { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDsParam[0].Uint64()) - } - b.StakedAmount = amountParam - b.StakedDuration = durationParam.Uint64() - b.UnlockedAt = maxStakingNumber - b.Muted = eh.muted - for i := 1; i < len(tokenIDsParam); i++ { - eh.delBucket(tokenIDsParam[i].Uint64()) - } - eh.putBucket(tokenIDsParam[0].Uint64(), b) - return nil -} - -func (eh *eventHandler) HandleBucketExpandedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - amountParam, err := event.FieldByIDUint256(1) - if err != nil { - return err - } - durationParam, err := event.FieldByIDUint256(2) - if err != nil { - return err - } - - b := eh.dirty.Bucket(tokenIDParam.Uint64()) - if b == nil { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) - } - b.StakedAmount = amountParam - b.StakedDuration = durationParam.Uint64() - b.Muted = eh.muted - eh.putBucket(tokenIDParam.Uint64(), b) - return nil -} - -func (eh *eventHandler) HandleDonatedEvent(event *abiutil.EventParam) error { - tokenIDParam, err := event.FieldByIDUint256(0) - if err != nil { - return err - } - amountParam, err := event.FieldByIDUint256(2) - if err != nil { - return err - } - - b := eh.dirty.Bucket(tokenIDParam.Uint64()) - if b == nil { - return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) - } - b.StakedAmount.Sub(b.StakedAmount, amountParam) - b.Muted = eh.muted - eh.putBucket(tokenIDParam.Uint64(), b) - return nil + return bkt, nil } func (eh *eventHandler) Finalize() (batch.KVStoreBatch, indexerCache) { @@ -296,75 +43,18 @@ func (eh *eventHandler) Finalize() (batch.KVStoreBatch, indexerCache) { return delta, dirty } -func (eh *eventHandler) putBucket(id uint64, bkt *Bucket) { +func (eh *eventHandler) PutBucket(_ address.Address, id uint64, bkt *Bucket) error { data, err := bkt.Serialize() if err != nil { - panic(errors.Wrap(err, "failed to serialize bucket")) + return errors.Wrap(err, "failed to serialize bucket") } eh.dirty.PutBucket(id, bkt) eh.delta.Put(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket") + return nil } -func (eh *eventHandler) delBucket(id uint64) { +func (eh *eventHandler) DeleteBucket(_ address.Address, id uint64) error { eh.dirty.DeleteBucket(id) eh.delta.Delete(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), "failed to delete bucket") -} - -func (eh *eventHandler) handleReceipt(ctx context.Context, contractAddr string, receipt *action.Receipt) error { - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - return nil - } - for _, log := range receipt.Logs() { - if log.Address != contractAddr { - continue - } - if err := eh.handleEvent(ctx, log); err != nil { - return err - } - } return nil } - -func (eh *eventHandler) handleEvent(ctx context.Context, actLog *action.Log) error { - // get event abi - abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) - if err != nil { - return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) - } - - // unpack event data - event, err := abiutil.UnpackEventParam(abiEvent, actLog) - if err != nil { - return err - } - log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) - // handle different kinds of event - switch abiEvent.Name { - case "Staked": - return eh.HandleStakedEvent(event) - case "Locked": - return eh.HandleLockedEvent(event) - case "Unlocked": - return eh.HandleUnlockedEvent(event) - case "Unstaked": - return eh.HandleUnstakedEvent(event) - case "Merged": - return eh.HandleMergedEvent(event) - case "BucketExpanded": - return eh.HandleBucketExpandedEvent(event) - case "DelegateChanged": - return eh.HandleDelegateChangedEvent(event) - case "Withdrawal": - return eh.HandleWithdrawalEvent(event) - case "Donated": - return eh.HandleDonatedEvent(event) - case "Transfer": - return eh.HandleTransferEvent(event) - case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", - "Migrated": - // not require handling events - return nil - default: - return errors.Errorf("unknown event name %s", abiEvent.Name) - } -} diff --git a/systemcontractindex/stakingindex/eventprocessor.go b/systemcontractindex/stakingindex/eventprocessor.go new file mode 100644 index 0000000000..95c7f8a2c9 --- /dev/null +++ b/systemcontractindex/stakingindex/eventprocessor.go @@ -0,0 +1,347 @@ +package stakingindex + +import ( + "context" + _ "embed" + "math" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + + "github.com/iotexproject/iotex-core/v2/action" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/pkg/util/abiutil" +) + +const ( + maxStakingNumber uint64 = math.MaxUint64 +) + +var ( + //go:embed staking_contract_abi_v3.json + stakingContractABIJSON string + // StakingContractABI is the abi of staking contract + StakingContractABI abi.ABI +) + +type ( + eventProcessor struct { + contractAddr address.Address + handler staking.EventHandler + tokenOwner map[uint64]address.Address + // context for event handler + blockCtx protocol.BlockCtx + timestamped bool + muted bool + } +) + +func init() { + var err error + StakingContractABI, err = abi.JSON(strings.NewReader(stakingContractABIJSON)) + if err != nil { + panic(err) + } +} + +func newEventProcessor(contractAddr address.Address, blkCtx protocol.BlockCtx, handler staking.EventHandler, timestamped, muted bool) *eventProcessor { + return &eventProcessor{ + contractAddr: contractAddr, + tokenOwner: make(map[uint64]address.Address), + blockCtx: blkCtx, + timestamped: timestamped, + muted: muted, + handler: handler, + } +} + +func (ep *eventProcessor) handleStakedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + delegateParam, err := event.FieldByIDAddress(1) + if err != nil { + return err + } + amountParam, err := event.FieldByIDUint256(2) + if err != nil { + return err + } + durationParam, err := event.FieldByIDUint256(3) + if err != nil { + return err + } + owner, ok := ep.tokenOwner[tokenIDParam.Uint64()] + if !ok { + return errors.Errorf("no owner for token id %d", tokenIDParam.Uint64()) + } + createdAt := ep.blockCtx.BlockHeight + if ep.timestamped { + createdAt = uint64(ep.blockCtx.BlockTimeStamp.Unix()) + } + bucket := &Bucket{ + Candidate: delegateParam, + Owner: owner, + StakedAmount: amountParam, + StakedDuration: durationParam.Uint64(), + CreatedAt: createdAt, + UnlockedAt: maxStakingNumber, + UnstakedAt: maxStakingNumber, + IsTimestampBased: ep.timestamped, + Muted: ep.muted, + } + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), bucket) +} + +func (ep *eventProcessor) handleLockedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + durationParam, err := event.FieldByIDUint256(1) + if err != nil { + return err + } + + bkt, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + bkt.StakedDuration = durationParam.Uint64() + bkt.UnlockedAt = maxStakingNumber + bkt.Muted = ep.muted + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), bkt) +} + +func (ep *eventProcessor) handleUnlockedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + + bkt, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + bkt.UnlockedAt = ep.blockCtx.BlockHeight + if ep.timestamped { + bkt.UnlockedAt = uint64(ep.blockCtx.BlockTimeStamp.Unix()) + } + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), bkt) +} + +func (ep *eventProcessor) handleUnstakedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + + bkt, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + bkt.UnstakedAt = ep.blockCtx.BlockHeight + if ep.timestamped { + bkt.UnstakedAt = uint64(ep.blockCtx.BlockTimeStamp.Unix()) + } + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), bkt) +} + +func (ep *eventProcessor) handleDelegateChangedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + delegateParam, err := event.FieldByIDAddress(1) + if err != nil { + return err + } + + bkt, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + bkt.Candidate = delegateParam + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), bkt) +} + +func (ep *eventProcessor) handleWithdrawalEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + + return ep.handler.DeleteBucket(ep.contractAddr, tokenIDParam.Uint64()) +} + +func (ep *eventProcessor) handleTransferEvent(event *abiutil.EventParam) error { + to, err := event.FieldByIDAddress(1) + if err != nil { + return err + } + tokenIDParam, err := event.FieldByIDUint256(2) + if err != nil { + return err + } + + tokenID := tokenIDParam.Uint64() + // cache token owner for stake event + ep.tokenOwner[tokenID] = to + // update bucket owner if token exists + bkt, err := ep.handler.DeductBucket(ep.contractAddr, tokenID) + switch errors.Cause(err) { + case nil: + bkt.Owner = to + return ep.handler.PutBucket(ep.contractAddr, tokenID, bkt) + case contractstaking.ErrBucketNotExist: + return nil + default: + return err + } +} + +func (ep *eventProcessor) handleMergedEvent(event *abiutil.EventParam) error { + tokenIDsParam, err := event.FieldByIDUint256Slice(0) + if err != nil { + return err + } + amountParam, err := event.FieldByIDUint256(1) + if err != nil { + return err + } + durationParam, err := event.FieldByIDUint256(2) + if err != nil { + return err + } + + // merge to the first bucket + b, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDsParam[0].Uint64()) + if err != nil { + return err + } + b.StakedAmount = amountParam + b.StakedDuration = durationParam.Uint64() + b.UnlockedAt = maxStakingNumber + b.Muted = ep.muted + for i := 1; i < len(tokenIDsParam); i++ { + if err := ep.handler.DeleteBucket(ep.contractAddr, tokenIDsParam[i].Uint64()); err != nil { + return err + } + } + return ep.handler.PutBucket(ep.contractAddr, tokenIDsParam[0].Uint64(), b) +} + +func (ep *eventProcessor) handleBucketExpandedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + amountParam, err := event.FieldByIDUint256(1) + if err != nil { + return err + } + durationParam, err := event.FieldByIDUint256(2) + if err != nil { + return err + } + + b, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + b.StakedAmount = amountParam + b.StakedDuration = durationParam.Uint64() + b.Muted = ep.muted + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), b) +} + +func (ep *eventProcessor) handleDonatedEvent(event *abiutil.EventParam) error { + tokenIDParam, err := event.FieldByIDUint256(0) + if err != nil { + return err + } + amountParam, err := event.FieldByIDUint256(2) + if err != nil { + return err + } + + b, err := ep.handler.DeductBucket(ep.contractAddr, tokenIDParam.Uint64()) + if err != nil { + return err + } + b.StakedAmount.Sub(b.StakedAmount, amountParam) + b.Muted = ep.muted + return ep.handler.PutBucket(ep.contractAddr, tokenIDParam.Uint64(), b) +} + +func (ep *eventProcessor) ProcessReceipts(ctx context.Context, receipts ...*action.Receipt) error { + expectedContractAddr := ep.contractAddr.String() + for _, receipt := range receipts { + if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { + continue + } + for _, log := range receipt.Logs() { + if log.Address != expectedContractAddr { + continue + } + if err := ep.processEvent(ctx, log); err != nil { + return errors.Wrapf(err, "handle %d log in receipt %x failed", log.Index, receipt.ActionHash) + } + } + } + return nil +} + +func (ep *eventProcessor) processEvent(ctx context.Context, actLog *action.Log) error { + // get event abi + abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) + if err != nil { + return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) + } + + // unpack event data + event, err := abiutil.UnpackEventParam(abiEvent, actLog) + if err != nil { + return err + } + log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) + // handle different kinds of event + switch abiEvent.Name { + case "Staked": + return ep.handleStakedEvent(event) + case "Locked": + return ep.handleLockedEvent(event) + case "Unlocked": + return ep.handleUnlockedEvent(event) + case "Unstaked": + return ep.handleUnstakedEvent(event) + case "Merged": + return ep.handleMergedEvent(event) + case "BucketExpanded": + return ep.handleBucketExpandedEvent(event) + case "DelegateChanged": + return ep.handleDelegateChangedEvent(event) + case "Withdrawal": + return ep.handleWithdrawalEvent(event) + case "Donated": + return ep.handleDonatedEvent(event) + case "Transfer": + return ep.handleTransferEvent(event) + case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", + "Migrated": + // not require handling events + return nil + default: + return errors.Errorf("unknown event name %s", abiEvent.Name) + } +} diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 00327cebc0..74041f01ef 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -14,10 +14,8 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" - "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" - "github.com/iotexproject/iotex-core/v2/pkg/util/abiutil" "github.com/iotexproject/iotex-core/v2/systemcontractindex" ) @@ -45,19 +43,7 @@ type ( TotalBucketCount(height uint64) (uint64, error) PutBlock(ctx context.Context, blk *block.Block) error LoadStakeView(context.Context, protocol.StateReader) (staking.ContractStakeView, error) - } - stakingEventHandler interface { - HandleStakedEvent(event *abiutil.EventParam) error - HandleLockedEvent(event *abiutil.EventParam) error - HandleUnlockedEvent(event *abiutil.EventParam) error - HandleUnstakedEvent(event *abiutil.EventParam) error - HandleDelegateChangedEvent(event *abiutil.EventParam) error - HandleWithdrawalEvent(event *abiutil.EventParam) error - HandleTransferEvent(event *abiutil.EventParam) error - HandleMergedEvent(event *abiutil.EventParam) error - HandleBucketExpandedEvent(event *abiutil.EventParam) error - HandleDonatedEvent(event *abiutil.EventParam) error - Finalize() (batch.KVStoreBatch, indexerCache) + CreateEventProcessor(context.Context, staking.EventHandler) staking.EventProcessor } // Indexer is the staking indexer Indexer struct { @@ -132,6 +118,18 @@ func (s *Indexer) Stop(ctx context.Context) error { return s.common.Stop(ctx) } +// CreateEventProcessor creates a new event processor +func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.EventHandler) staking.EventProcessor { + blkCtx := protocol.MustGetBlockCtx(ctx) + return newEventProcessor( + s.common.ContractAddress(), + blkCtx, + handler, + s.timestamped, + s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight, + ) +} + // LoadStakeView loads the contract stake view from state reader func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { s.mutex.RLock() @@ -303,17 +301,22 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { } // handle events of block muted := s.muteHeight > 0 && blk.Height() >= s.muteHeight - handler := newEventHandler(s.bucketNS, newWrappedCache(s.cache), protocol.MustGetBlockCtx(ctx), s.timestamped, muted) - for _, receipt := range blk.Receipts { - if err := handler.handleReceipt(ctx, s.common.ContractAddress().String(), receipt); err != nil { - return errors.Wrapf(err, "handle receipt %x failed", receipt.ActionHash) - } + handler := newEventHandler(s.bucketNS, newWrappedCache(s.cache)) + processor := newEventProcessor( + s.common.ContractAddress(), + protocol.MustGetBlockCtx(ctx), + handler, + s.timestamped, + muted, + ) + if err := processor.ProcessReceipts(ctx, blk.Receipts...); err != nil { + return err } // commit return s.commit(ctx, handler, blk.Height()) } -func (s *Indexer) commit(ctx context.Context, handler stakingEventHandler, height uint64) error { +func (s *Indexer) commit(ctx context.Context, handler *eventHandler, height uint64) error { delta, dirty := handler.Finalize() // update db if err := s.common.Commit(height, delta); err != nil { diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index dd49e80278..8d92ab3ecb 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -92,8 +92,9 @@ func (s *stakeView) CreatePreStates(ctx context.Context) error { func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight - handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) - return handler.handleReceipt(ctx, s.contractAddr.String(), receipt) + return newEventProcessor( + s.contractAddr, blkCtx, newEventHandler(s.bucketNS, s.cache), s.timestamped, muted, + ).ProcessReceipts(ctx, receipt) } func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { @@ -107,11 +108,10 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec } ctx = protocol.WithBlockCtx(ctx, blkCtx) muted := s.muteHeight > 0 && height >= s.muteHeight - handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) - for _, receipt := range receipts { - if err := handler.handleReceipt(ctx, s.contractAddr.String(), receipt); err != nil { - return errors.Wrapf(err, "failed to handle receipt at height %d", height) - } + if err := newEventProcessor( + s.contractAddr, blkCtx, newEventHandler(s.bucketNS, s.cache), s.timestamped, muted, + ).ProcessReceipts(ctx, receipts...); err != nil { + return errors.Wrapf(err, "failed to handle receipts at height %d", height) } s.height = height return nil diff --git a/test/mock/mock_chainmanager/mock_chainmanager.go b/test/mock/mock_chainmanager/mock_chainmanager.go index 89815724ac..acf4d711d4 100644 --- a/test/mock/mock_chainmanager/mock_chainmanager.go +++ b/test/mock/mock_chainmanager/mock_chainmanager.go @@ -285,3 +285,190 @@ func (mr *MockStateManagerMockRecorder) WriteView(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteView", reflect.TypeOf((*MockStateManager)(nil).WriteView), arg0, arg1) } + +// MockStateManagerWithCloser is a mock of StateManagerWithCloser interface. +type MockStateManagerWithCloser struct { + ctrl *gomock.Controller + recorder *MockStateManagerWithCloserMockRecorder + isgomock struct{} +} + +// MockStateManagerWithCloserMockRecorder is the mock recorder for MockStateManagerWithCloser. +type MockStateManagerWithCloserMockRecorder struct { + mock *MockStateManagerWithCloser +} + +// NewMockStateManagerWithCloser creates a new mock instance. +func NewMockStateManagerWithCloser(ctrl *gomock.Controller) *MockStateManagerWithCloser { + mock := &MockStateManagerWithCloser{ctrl: ctrl} + mock.recorder = &MockStateManagerWithCloserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateManagerWithCloser) EXPECT() *MockStateManagerWithCloserMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockStateManagerWithCloser) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close. +func (mr *MockStateManagerWithCloserMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStateManagerWithCloser)(nil).Close)) +} + +// DelState mocks base method. +func (m *MockStateManagerWithCloser) DelState(arg0 ...protocol.StateOption) (uint64, error) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DelState", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DelState indicates an expected call of DelState. +func (mr *MockStateManagerWithCloserMockRecorder) DelState(arg0 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelState", reflect.TypeOf((*MockStateManagerWithCloser)(nil).DelState), arg0...) +} + +// Height mocks base method. +func (m *MockStateManagerWithCloser) Height() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Height") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Height indicates an expected call of Height. +func (mr *MockStateManagerWithCloserMockRecorder) Height() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockStateManagerWithCloser)(nil).Height)) +} + +// PutState mocks base method. +func (m *MockStateManagerWithCloser) PutState(arg0 any, arg1 ...protocol.StateOption) (uint64, error) { + m.ctrl.T.Helper() + varargs := []any{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PutState", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PutState indicates an expected call of PutState. +func (mr *MockStateManagerWithCloserMockRecorder) PutState(arg0 any, arg1 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutState", reflect.TypeOf((*MockStateManagerWithCloser)(nil).PutState), varargs...) +} + +// ReadView mocks base method. +func (m *MockStateManagerWithCloser) ReadView(arg0 string) (protocol.View, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadView", arg0) + ret0, _ := ret[0].(protocol.View) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadView indicates an expected call of ReadView. +func (mr *MockStateManagerWithCloserMockRecorder) ReadView(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadView", reflect.TypeOf((*MockStateManagerWithCloser)(nil).ReadView), arg0) +} + +// Revert mocks base method. +func (m *MockStateManagerWithCloser) Revert(arg0 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Revert", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Revert indicates an expected call of Revert. +func (mr *MockStateManagerWithCloserMockRecorder) Revert(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Revert", reflect.TypeOf((*MockStateManagerWithCloser)(nil).Revert), arg0) +} + +// Snapshot mocks base method. +func (m *MockStateManagerWithCloser) Snapshot() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Snapshot") + ret0, _ := ret[0].(int) + return ret0 +} + +// Snapshot indicates an expected call of Snapshot. +func (mr *MockStateManagerWithCloserMockRecorder) Snapshot() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Snapshot", reflect.TypeOf((*MockStateManagerWithCloser)(nil).Snapshot)) +} + +// State mocks base method. +func (m *MockStateManagerWithCloser) State(arg0 any, arg1 ...protocol.StateOption) (uint64, error) { + m.ctrl.T.Helper() + varargs := []any{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "State", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// State indicates an expected call of State. +func (mr *MockStateManagerWithCloserMockRecorder) State(arg0 any, arg1 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockStateManagerWithCloser)(nil).State), varargs...) +} + +// States mocks base method. +func (m *MockStateManagerWithCloser) States(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "States", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(state.Iterator) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// States indicates an expected call of States. +func (mr *MockStateManagerWithCloserMockRecorder) States(arg0 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "States", reflect.TypeOf((*MockStateManagerWithCloser)(nil).States), arg0...) +} + +// WriteView mocks base method. +func (m *MockStateManagerWithCloser) WriteView(arg0 string, arg1 protocol.View) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteView", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteView indicates an expected call of WriteView. +func (mr *MockStateManagerWithCloserMockRecorder) WriteView(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteView", reflect.TypeOf((*MockStateManagerWithCloser)(nil).WriteView), arg0, arg1) +} From 50394135acbc0e701194d417b28d9186e4f96b98 Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 2 Sep 2025 00:54:27 +0800 Subject: [PATCH 16/34] refactor two functions --- .../protocol/staking/candidate_statereader.go | 37 ++++++++++--------- action/protocol/staking/candidate_test.go | 3 +- action/protocol/staking/protocol_test.go | 11 ++++-- action/protocol/staking/viewdata.go | 10 ++--- action/protocol/staking/vote_reviser.go | 7 ++-- blockindex/contractstaking/stakeview.go | 2 +- systemcontractindex/stakingindex/stakeview.go | 2 +- 7 files changed, 39 insertions(+), 33 deletions(-) diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index fd6505b1bd..8d0ad5f6b8 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -46,7 +46,7 @@ type ( NativeBucketIndicesByVoter(addr address.Address) (*BucketIndices, uint64, error) NativeBucketIndicesByCandidate(addr address.Address) (*BucketIndices, uint64, error) CandidateByAddress(name address.Address) (*Candidate, uint64, error) - getAllCandidates() (CandidateList, uint64, error) + CreateCandidateCenter() (*CandidateCenter, uint64, error) ReadState Height() uint64 SR() protocol.StateReader @@ -152,12 +152,7 @@ func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*viewData, u } csr := newCandidateStateReader(sr) - all, height, err := csr.getAllCandidates() - if err != nil && errors.Cause(err) != state.ErrStateNotExist { - return nil, height, err - } - - center, err := NewCandidateCenter(all) + center, height, err := csr.CreateCandidateCenter() if err != nil { return nil, height, err } @@ -302,21 +297,29 @@ func (c *candSR) CandidateByAddress(name address.Address) (*Candidate, uint64, e return &d, height, err } -func (c *candSR) getAllCandidates() (CandidateList, uint64, error) { +func (c *candSR) CreateCandidateCenter() (*CandidateCenter, uint64, error) { height, iter, err := c.States(protocol.NamespaceOption(_candidateNameSpace), protocol.ObjectOption(&Candidate{})) + var cands CandidateList + switch errors.Cause(err) { + case nil: + cands = make(CandidateList, 0, iter.Size()) + for i := 0; i < iter.Size(); i++ { + c := &Candidate{} + if _, err := iter.Next(c); err != nil { + return nil, height, errors.Wrapf(err, "failed to deserialize candidate") + } + cands = append(cands, c) + } + case state.ErrStateNotExist: + default: + return nil, height, err + } + center, err := NewCandidateCenter(cands) if err != nil { return nil, height, err } - cands := make(CandidateList, 0, iter.Size()) - for i := 0; i < iter.Size(); i++ { - c := &Candidate{} - if _, err := iter.Next(c); err != nil { - return nil, height, errors.Wrapf(err, "failed to deserialize candidate") - } - cands = append(cands, c) - } - return cands, height, nil + return center, height, nil } func (c *candSR) NewBucketPool(enableSMStorage bool) (*BucketPool, error) { diff --git a/action/protocol/staking/candidate_test.go b/action/protocol/staking/candidate_test.go index 56ddad626b..3ffb380f43 100644 --- a/action/protocol/staking/candidate_test.go +++ b/action/protocol/staking/candidate_test.go @@ -214,8 +214,9 @@ func TestGetPutCandidate(t *testing.T) { } // get all candidates - all, _, err := csr.getAllCandidates() + cc, _, err := csr.CreateCandidateCenter() require.NoError(err) + all := cc.All() require.Equal(len(testCandidates), len(all)) for _, e := range testCandidates { for i := range all { diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 6a62ebeafe..afa55c88d7 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -102,9 +102,9 @@ func TestProtocol(t *testing.T) { buckets, _, err := csr.NativeBuckets() r.NoError(err) r.Equal(0, len(buckets)) - c, _, err := csr.getAllCandidates() - r.Equal(state.ErrStateNotExist, err) - r.Equal(0, len(c)) + cc, _, err := csr.CreateCandidateCenter() + r.NoError(err) + r.Equal(0, len(cc.All())) // address package also defined protocol address, make sure they match r.Equal(stk.addr.Bytes(), address.StakingProtocolAddrHash[:]) @@ -162,8 +162,9 @@ func TestProtocol(t *testing.T) { } // load all candidates from stateDB and verify - all, _, err := csr.getAllCandidates() + cc, _, err = csr.CreateCandidateCenter() r.NoError(err) + all := cc.All() r.Equal(len(testCandidates), len(all)) for _, e := range testCandidates { for i := range all { @@ -188,8 +189,10 @@ func TestProtocol(t *testing.T) { // delete one bucket r.NoError(csm.delBucket(1)) buckets, _, err = csr.NativeBuckets() + require.NoError(t, err) r.NoError(csm.delBucket(1)) buckets, _, err = csr.NativeBuckets() + require.NoError(t, err) for _, e := range tests { for i := range buckets { if buckets[i].StakedAmount == e.amount { diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index ece4958849..10f0f9dac5 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -30,8 +30,8 @@ type ( CreatePreStates(ctx context.Context) error // Handle handles the receipt for the contract stake view Handle(ctx context.Context, receipt *action.Receipt) error - // WriteBuckets writes the buckets to the state manager - WriteBuckets(protocol.StateManager) error + // Migrate migrate the buckets to the state manager + Migrate(protocol.StateManager) error // BucketsByCandidate returns the buckets by candidate address BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error @@ -130,17 +130,17 @@ func (v *viewData) Revert(snapshot int) error { func (csv *contractStakeView) FlushBuckets(sm protocol.StateManager) error { if csv.v1 != nil { - if err := csv.v1.WriteBuckets(sm); err != nil { + if err := csv.v1.Migrate(sm); err != nil { return err } } if csv.v2 != nil { - if err := csv.v2.WriteBuckets(sm); err != nil { + if err := csv.v2.Migrate(sm); err != nil { return err } } if csv.v3 != nil { - if err := csv.v3.WriteBuckets(sm); err != nil { + if err := csv.v3.Migrate(sm); err != nil { return err } } diff --git a/action/protocol/staking/vote_reviser.go b/action/protocol/staking/vote_reviser.go index 844d0c324a..472fb99494 100644 --- a/action/protocol/staking/vote_reviser.go +++ b/action/protocol/staking/vote_reviser.go @@ -42,12 +42,11 @@ func NewVoteReviser(cfg ReviseConfig) *VoteReviser { // Revise recalculate candidate votes on preset revising height. func (vr *VoteReviser) Revise(ctx protocol.FeatureCtx, csm CandidateStateManager, height uint64) error { if !vr.isCacheExist(height) { - cands, _, err := newCandidateStateReader(csm.SM()).getAllCandidates() - switch { - case errors.Cause(err) == state.ErrStateNotExist: - case err != nil: + cc, _, err := newCandidateStateReader(csm.SM()).CreateCandidateCenter() + if err != nil { return err } + cands := cc.All() if vr.shouldCorrectCandSelfStake(height) { cands, err = vr.correctCandSelfStake(ctx, csm, height, cands) if err != nil { diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index 22650faf08..f9dd38516c 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -56,7 +56,7 @@ func (s *stakeView) IsDirty() bool { return s.cache.IsDirty() } -func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { +func (s *stakeView) Migrate(sm protocol.StateManager) error { ids, types, infos := s.cache.Buckets() cssm := contractstaking.NewContractStakingStateManager(sm) bucketMap := make(map[uint64]*bucketInfo, len(ids)) diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 8d92ab3ecb..5b18dd65fc 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -54,7 +54,7 @@ func (s *stakeView) IsDirty() bool { return s.cache.IsDirty() } -func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { +func (s *stakeView) Migrate(sm protocol.StateManager) error { ids := s.cache.BucketIdxs() slices.Sort(ids) buckets := s.cache.Buckets(ids) From a2ebe850323c5ac000cc4033e2e660f87c35e537 Mon Sep 17 00:00:00 2001 From: zhi Date: Sat, 30 Aug 2025 20:18:36 +0800 Subject: [PATCH 17/34] candidate center with nft staking --- action/protocol/context.go | 4 +- action/protocol/staking/builder.go | 11 +- .../staking/candidate_statereader_test.go | 4 +- .../staking/contractstaking/statereader.go | 4 + .../handler_candidate_selfstake_test.go | 7 +- ...ndler_candidate_transfer_ownership_test.go | 7 +- .../staking/handler_stake_migrate_test.go | 7 +- action/protocol/staking/handlers_test.go | 14 +- action/protocol/staking/nfteventhandler.go | 134 ++++++++++++++++ action/protocol/staking/protocol.go | 143 +++++++++++++++--- action/protocol/staking/protocol_test.go | 35 +++-- .../staking/staking_statereader_test.go | 4 +- action/protocol/staking/validations_test.go | 2 +- action/protocol/staking/viewdata.go | 28 ++-- action/protocol/staking/vote_bucket.go | 6 +- action/protocol/staking/vote_bucket_test.go | 2 +- action/protocol/staking/vote_reviser_test.go | 7 +- blockindex/contractstaking/cache.go | 18 ++- .../contractstaking/dirty_cache_test.go | 16 +- blockindex/contractstaking/indexer.go | 14 +- blockindex/contractstaking/stakeview.go | 18 ++- blockindex/contractstaking/wrappedcache.go | 14 ++ chainservice/builder.go | 32 ++-- go.mod | 2 +- go.sum | 4 +- state/factory/factory_test.go | 6 +- .../stakingindex/eventprocessor.go | 3 +- systemcontractindex/stakingindex/index.go | 10 +- systemcontractindex/stakingindex/stakeview.go | 8 +- 29 files changed, 425 insertions(+), 139 deletions(-) create mode 100644 action/protocol/staking/nfteventhandler.go diff --git a/action/protocol/context.go b/action/protocol/context.go index 4153b97d7b..6ef68e1ffe 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -162,7 +162,7 @@ type ( NotSlashUnproductiveDelegates bool CandidateBLSPublicKey bool NotUseMinSelfStakeToBeActive bool - LoadContractStakingFromIndexer bool + StoreVoteOfNFTBucketIntoView bool } // FeatureWithHeightCtx provides feature check functions. @@ -328,7 +328,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { NotSlashUnproductiveDelegates: !g.IsToBeEnabled(height), CandidateBLSPublicKey: g.IsToBeEnabled(height), NotUseMinSelfStakeToBeActive: !g.IsToBeEnabled(height), - LoadContractStakingFromIndexer: !g.IsToBeEnabled(height), + StoreVoteOfNFTBucketIntoView: !g.IsToBeEnabled(height), }, ) } diff --git a/action/protocol/staking/builder.go b/action/protocol/staking/builder.go index ba213f052c..84a7723c4c 100644 --- a/action/protocol/staking/builder.go +++ b/action/protocol/staking/builder.go @@ -6,10 +6,11 @@ type ( // BuilderConfig returns the configuration of the builder BuilderConfig struct { - Staking genesis.Staking - PersistStakingPatchBlock uint64 - FixAliasForNonStopHeight uint64 - StakingPatchDir string - Revise ReviseConfig + Staking genesis.Staking + PersistStakingPatchBlock uint64 + FixAliasForNonStopHeight uint64 + SkipContractStakingViewHeight uint64 + StakingPatchDir string + Revise ReviseConfig } ) diff --git a/action/protocol/staking/candidate_statereader_test.go b/action/protocol/staking/candidate_statereader_test.go index 1fd1e62308..82a85438bc 100644 --- a/action/protocol/staking/candidate_statereader_test.go +++ b/action/protocol/staking/candidate_statereader_test.go @@ -22,11 +22,11 @@ func Test_CandidateStateReader(t *testing.T) { sm := testdb.NewMockStateManager(ctrl) h, err := sm.Height() require.NoError(err) - csr, err := ConstructBaseView(sm) + _, err = ConstructBaseView(sm) require.Equal(err, protocol.ErrNoName) view, _, err := CreateBaseView(sm, false) require.NoError(err) - csr = &candSR{ + csr := &candSR{ StateReader: sm, height: h, view: view, diff --git a/action/protocol/staking/contractstaking/statereader.go b/action/protocol/staking/contractstaking/statereader.go index 3058b07c85..98b0d542d2 100644 --- a/action/protocol/staking/contractstaking/statereader.go +++ b/action/protocol/staking/contractstaking/statereader.go @@ -88,6 +88,10 @@ func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucket contractNamespaceOption(contractAddr), bucketIDKeyOption(bucketID), ); err != nil { + switch errors.Cause(err) { + case state.ErrStateNotExist: + return nil, errors.Wrapf(ErrBucketNotExist, "bucket %d for contract %s", bucketID, contractAddr.String()) + } return nil, err } diff --git a/action/protocol/staking/handler_candidate_selfstake_test.go b/action/protocol/staking/handler_candidate_selfstake_test.go index 8ca0d334ce..1449159d9b 100644 --- a/action/protocol/staking/handler_candidate_selfstake_test.go +++ b/action/protocol/staking/handler_candidate_selfstake_test.go @@ -88,12 +88,13 @@ func initTestStateWithHeight(t *testing.T, ctrl *gomock.Controller, bucketCfgs [ DepositGas: depositGas, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) // set up bucket diff --git a/action/protocol/staking/handler_candidate_transfer_ownership_test.go b/action/protocol/staking/handler_candidate_transfer_ownership_test.go index 9731caff3f..3e41aa035e 100644 --- a/action/protocol/staking/handler_candidate_transfer_ownership_test.go +++ b/action/protocol/staking/handler_candidate_transfer_ownership_test.go @@ -40,12 +40,13 @@ func TestProtocol_HandleCandidateTransferOwnership(t *testing.T) { DepositGas: depositGas, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) initCandidateCfgs := []struct { Owner address.Address diff --git a/action/protocol/staking/handler_stake_migrate_test.go b/action/protocol/staking/handler_stake_migrate_test.go index 0b98ca9fb1..f1d387bae8 100644 --- a/action/protocol/staking/handler_stake_migrate_test.go +++ b/action/protocol/staking/handler_stake_migrate_test.go @@ -42,13 +42,14 @@ func TestHandleStakeMigrate(t *testing.T) { p, err := NewProtocol( HelperCtx{getBlockInterval, depositGas}, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, }, - nil, nil, nil) + nil, nil, nil, nil) r.NoError(err) cfg := deepcopy.Copy(genesis.TestDefault()).(genesis.Genesis) initCfg := func(cfg *genesis.Genesis) { diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index 7aa9bf5236..7dae3aea12 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -94,12 +94,13 @@ func TestProtocol_HandleCreateStake(t *testing.T) { DepositGas: depositGas, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: genesis.TestDefault().Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: genesis.TestDefault().Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: genesis.TestDefault().Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) // set up candidate @@ -3336,12 +3337,13 @@ func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Pro DepositGas: depositGas, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) // set up candidate diff --git a/action/protocol/staking/nfteventhandler.go b/action/protocol/staking/nfteventhandler.go new file mode 100644 index 0000000000..009fe90594 --- /dev/null +++ b/action/protocol/staking/nfteventhandler.go @@ -0,0 +1,134 @@ +package staking + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/pkg/errors" +) + +type ( + // CalculateVoteWeightFunc is a function that calculates the vote weight of a bucket. + CalculateVoteWeightFunc func(bkt *contractstaking.Bucket, height uint64) *big.Int + + nftEventHandler struct { + calculateVoteWeight CalculateVoteWeightFunc + cssm *contractstaking.ContractStakingStateManager + csm CandidateStateManager + bucketTypes map[address.Address]map[uint64]*contractstaking.BucketType + bucketTypesLookup map[address.Address]map[int64]map[uint64]uint64 // contract -> amount -> duration -> id + } +) + +func newNFTBucketEventHandler(sm protocol.StateManager, calculateVoteWeight CalculateVoteWeightFunc) (*nftEventHandler, error) { + csm, err := NewCandidateStateManager(sm) + if err != nil { + return nil, err + } + return &nftEventHandler{ + calculateVoteWeight: calculateVoteWeight, + cssm: contractstaking.NewContractStakingStateManager(sm), + csm: csm, + bucketTypes: make(map[address.Address]map[uint64]*contractstaking.BucketType), + bucketTypesLookup: make(map[address.Address]map[int64]map[uint64]uint64), + }, nil +} + +func (handler *nftEventHandler) matchBucketType(contractAddr address.Address, amount *big.Int, duration uint64) (uint64, error) { + cmap, ok := handler.bucketTypesLookup[contractAddr] + if !ok { + tids, bucketTypes, err := handler.cssm.BucketTypes(contractAddr) + if err != nil { + return 0, err + } + cmap = make(map[int64]map[uint64]uint64) + bts := make(map[uint64]*contractstaking.BucketType, len(tids)) + for i, bt := range bucketTypes { + amount := bt.Amount.Int64() + if cmap[amount] == nil { + cmap[amount] = make(map[uint64]uint64) + } + cmap[amount][bt.Duration] = tids[i] + bts[tids[i]] = bt + } + handler.bucketTypesLookup[contractAddr] = cmap + handler.bucketTypes[contractAddr] = bts + } + amap, ok := cmap[amount.Int64()] + if !ok { + return uint64(len(cmap)), nil + } + id, ok := amap[duration] + if !ok { + return uint64(len(cmap)), nil + } + + return id, nil +} + +func (handler *nftEventHandler) PutBucketType(contractAddr address.Address, bt *contractstaking.BucketType) error { + id, err := handler.matchBucketType(contractAddr, bt.Amount, bt.Duration) + if err != nil { + return err + } + if err := handler.cssm.UpsertBucketType(contractAddr, id, bt); err != nil { + return err + } + handler.bucketTypes[contractAddr][id] = bt + return nil +} + +func (handler *nftEventHandler) DeductBucket(contractAddr address.Address, id uint64) (*contractstaking.Bucket, error) { + bucket, err := handler.cssm.Bucket(contractAddr, id) + if err != nil { + return nil, errors.Wrap(err, "failed to get bucket") + } + height, err := handler.csm.SR().Height() + if err != nil { + return nil, errors.Wrap(err, "failed to get height") + } + candidate := handler.csm.GetByIdentifier(bucket.Candidate) + if err := candidate.SubVote(handler.calculateVoteWeight(bucket, height)); err != nil { + return nil, errors.Wrap(err, "failed to subtract vote") + } + if err := handler.csm.Upsert(candidate); err != nil { + return nil, errors.Wrap(err, "failed to upsert candidate") + } + return bucket, nil +} + +func (handler *nftEventHandler) PutBucket(contractAddr address.Address, id uint64, bkt *contractstaking.Bucket) error { + height, err := handler.csm.SR().Height() + if err != nil { + return errors.Wrap(err, "failed to get height") + } + candidate := handler.csm.GetByIdentifier(bkt.Candidate) + if err := candidate.AddVote(handler.calculateVoteWeight(bkt, height)); err != nil { + return errors.Wrap(err, "failed to add vote") + } + if err := handler.cssm.UpsertBucket(contractAddr, id, bkt); err != nil { + return errors.Wrap(err, "failed to put bucket") + } + return handler.csm.Upsert(candidate) +} + +func (handler *nftEventHandler) DeleteBucket(contractAddr address.Address, id uint64) error { + bucket, err := handler.cssm.Bucket(contractAddr, id) + if err != nil { + return errors.Wrap(err, "failed to get bucket") + } + height, err := handler.csm.SR().Height() + if err != nil { + return errors.Wrap(err, "failed to get height") + } + candidate := handler.csm.GetByIdentifier(bucket.Candidate) + if err := candidate.SubVote(handler.calculateVoteWeight(bucket, height)); err != nil { + return errors.Wrap(err, "failed to subtract vote") + } + if err := handler.cssm.DeleteBucket(contractAddr, id); err != nil { + return errors.Wrap(err, "failed to delete bucket") + } + return handler.csm.Upsert(candidate) +} diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index a57112df3b..c99cb63cd8 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -8,6 +8,7 @@ package staking import ( "context" "encoding/hex" + "math" "math/big" "time" @@ -23,6 +24,7 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" accountutil "github.com/iotexproject/iotex-core/v2/action/protocol/account/util" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" @@ -40,6 +42,9 @@ const ( // CandsMapNS is the bucket name to store candidate map CandsMapNS = state.CandsMapNamespace + + // MaxDurationNumber is the maximum duration number + MaxDurationNumber = math.MaxUint64 ) const ( @@ -88,6 +93,7 @@ type ( patch *PatchStore helperCtx HelperCtx blockStore BlockStore + blocksToDurationFn func(startHeight, endHeight, currentHeight uint64) time.Duration } // Configuration is the staking protocol configuration. @@ -99,6 +105,7 @@ type ( BootstrapCandidates []genesis.BootstrapCandidate PersistStakingPatchBlock uint64 FixAliasForNonStopHeight uint64 + SkipContractStakingViewHeight uint64 EndorsementWithdrawWaitingBlocks uint64 MigrateContractAddress string TimestampedMigrateContractAddress string @@ -149,7 +156,8 @@ func FindProtocol(registry *protocol.Registry) *Protocol { func NewProtocol( helperCtx HelperCtx, cfg *BuilderConfig, - _ *CandidatesBucketsIndexer, // TODO: remove this parameter + blocksToDurationFn func(startHeight, endHeight, currentHeight uint64) time.Duration, + candBucketsIndexer *CandidatesBucketsIndexer, contractStakingIndexer ContractStakingIndexerWithBucketType, contractStakingIndexerV2 ContractStakingIndexer, opts ...Option, @@ -200,9 +208,12 @@ func NewProtocol( BootstrapCandidates: cfg.Staking.BootstrapCandidates, PersistStakingPatchBlock: cfg.PersistStakingPatchBlock, FixAliasForNonStopHeight: cfg.FixAliasForNonStopHeight, + SkipContractStakingViewHeight: cfg.SkipContractStakingViewHeight, EndorsementWithdrawWaitingBlocks: cfg.Staking.EndorsementWithdrawWaitingBlocks, MigrateContractAddress: migrateContractAddress, }, + blocksToDurationFn: blocksToDurationFn, + candBucketsIndexer: candBucketsIndexer, voteReviser: voteReviser, patch: NewPatchStore(cfg.StakingPatchDir), contractStakingIndexer: contractStakingIndexer, @@ -246,7 +257,9 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol return nil, errors.Wrap(err, "failed to load name/operator map to cand center") } } - + if p.skipContractStakingView(height) { + return c, nil + } c.contractsStake = &contractStakeView{} if p.contractStakingIndexer != nil { view, err := NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore).Build(ctx, sr, height) @@ -425,13 +438,23 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager if err != nil { return err } + vd := v.(*viewData) if blkCtx.BlockHeight == g.ToBeEnabledBlockHeight { - if err := v.(*viewData).contractsStake.FlushBuckets(sm); err != nil { - return errors.Wrap(err, "failed to write buckets") + handler, err := newNFTBucketEventHandler(sm, func(bucket *contractstaking.Bucket, height uint64) *big.Int { + vb := p.convertToVoteBucket(bucket, height) + return p.calculateVoteWeight(vb, false) + }) + if err != nil { + return err + } + if err := vd.contractsStake.Migrate(handler); err != nil { + return errors.Wrap(err, "failed to flush buckets for contract staking") } } - if err = v.(*viewData).contractsStake.CreatePreStates(ctx); err != nil { - return err + if featureCtx.StoreVoteOfNFTBucketIntoView { + if err := vd.contractsStake.CreatePreStates(ctx); err != nil { + return err + } } return nil } @@ -564,11 +587,43 @@ func (p *Protocol) handle(ctx context.Context, elp action.Envelope, csm Candidat // HandleReceipt handles a receipt func (p *Protocol) HandleReceipt(ctx context.Context, elp action.Envelope, sm protocol.StateManager, receipt *action.Receipt) error { - v, err := sm.ReadView(_protocolID) + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok { + return errors.New("failed to get feature context from action context") + } + if featureCtx.StoreVoteOfNFTBucketIntoView { + v, err := sm.ReadView(_protocolID) + if err != nil { + return err + } + return v.(*viewData).contractsStake.Handle(ctx, receipt) + } + handler, err := newNFTBucketEventHandler(sm, func(bucket *contractstaking.Bucket, height uint64) *big.Int { + vb := p.convertToVoteBucket(bucket, height) + return p.calculateVoteWeight(vb, false) + }) if err != nil { return err } - return v.(*viewData).contractsStake.Handle(ctx, receipt) + if p.contractStakingIndexer != nil { + processor := p.contractStakingIndexer.CreateEventProcessor(ctx, handler) + if err := processor.ProcessReceipts(ctx, receipt); err != nil { + return errors.Wrap(err, "failed to process receipt for contract staking indexer") + } + } + if p.contractStakingIndexerV2 != nil { + processor := p.contractStakingIndexerV2.CreateEventProcessor(ctx, handler) + if err := processor.ProcessReceipts(ctx, receipt); err != nil { + return errors.Wrap(err, "failed to process receipt for contract staking indexer v2") + } + } + if p.contractStakingIndexerV3 != nil { + processor := p.contractStakingIndexerV3.CreateEventProcessor(ctx, handler) + if err := processor.ProcessReceipts(ctx, receipt); err != nil { + return errors.Wrap(err, "failed to process receipt for contract staking indexer v3") + } + } + return nil } // Validate validates a staking message @@ -651,22 +706,24 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader list := c.AllCandidates() cand := make(CandidateList, 0, len(list)) for i := range list { - var csVotes *big.Int - if protocol.MustGetFeatureCtx(ctx).CreatePostActionStates { - csVotes, err = p.contractStakingVotesFromView(ctx, list[i].GetIdentifier(), c.BaseView()) - if err != nil { - return nil, err + if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { + var csVotes *big.Int + if protocol.MustGetFeatureCtx(ctx).CreatePostActionStates { + csVotes, err = p.contractStakingVotesFromView(ctx, list[i].GetIdentifier(), c.BaseView()) + if err != nil { + return nil, err + } + } else { + // specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind. + // the reason of using srHeight-1 is contract indexer is not updated before the block is committed. + csVotes, err = p.contractStakingVotesFromIndexer(ctx, list[i].GetIdentifier(), srHeight-1) + if err != nil { + return nil, err + } } - } else { - // specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind. - // the reason of using srHeight-1 is contract indexer is not updated before the block is committed. - csVotes, err = p.contractStakingVotesFromIndexer(ctx, list[i].GetIdentifier(), srHeight-1) - if err != nil { - return nil, err - } - } - list[i].Votes.Add(list[i].Votes, csVotes) + list[i].Votes.Add(list[i].Votes, csVotes) + } active, err := p.isActiveCandidate(ctx, c, list[i]) if err != nil { return nil, err @@ -778,6 +835,44 @@ func (p *Protocol) Name() string { return _protocolID } +func (p *Protocol) convertToVoteBucket(bkt *contractstaking.Bucket, height uint64) *VoteBucket { + vb := VoteBucket{ + Index: 0, + StakedAmount: bkt.StakedAmount, + AutoStake: bkt.UnlockedAt == MaxDurationNumber, + Candidate: bkt.Candidate, + Owner: bkt.Owner, + ContractAddress: "", + Timestamped: bkt.IsTimestampBased, + } + if bkt.IsTimestampBased { + vb.StakedDuration = time.Duration(bkt.StakedDuration) * time.Second + vb.StakeStartTime = time.Unix(int64(bkt.CreatedAt), 0) + vb.CreateTime = time.Unix(int64(bkt.CreatedAt), 0) + if bkt.UnlockedAt != MaxDurationNumber { + vb.StakeStartTime = time.Unix(int64(bkt.UnlockedAt), 0) + } + if bkt.UnstakedAt == MaxDurationNumber { + vb.UnstakeStartTime = time.Unix(0, 0) + } else { + vb.UnstakeStartTime = time.Unix(int64(bkt.UnstakedAt), 0) + } + } else { + vb.StakedDuration = p.blocksToDurationFn(bkt.CreatedAt, bkt.CreatedAt+bkt.StakedDuration, height) + vb.StakedDurationBlockNumber = bkt.StakedDuration + vb.CreateBlockHeight = bkt.CreatedAt + vb.StakeStartBlockHeight = bkt.CreatedAt + vb.UnstakeStartBlockHeight = bkt.UnstakedAt + if bkt.UnlockedAt != MaxDurationNumber { + vb.StakeStartBlockHeight = bkt.UnlockedAt + } + } + if bkt.Muted { + vb.Candidate, _ = address.FromString(address.ZeroAddress) + } + return &vb +} + func (p *Protocol) calculateVoteWeight(v *VoteBucket, selfStake bool) *big.Int { return CalculateVoteWeight(p.config.VoteWeightCalConsts, v, selfStake) } @@ -841,6 +936,10 @@ func (p *Protocol) settleAction( return &r, nil } +func (p *Protocol) skipContractStakingView(height uint64) bool { + return height >= p.config.SkipContractStakingViewHeight +} + func (p *Protocol) needToReadCandsMap(ctx context.Context, height uint64) bool { fCtx := protocol.MustGetFeatureWithHeightCtx(ctx) return height > p.config.PersistStakingPatchBlock && fCtx.CandCenterHasAlias(height) diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index afa55c88d7..6ba3b57199 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -91,12 +91,13 @@ func TestProtocol(t *testing.T) { DepositGas: nil, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) r.NotNil(stk) r.NoError(err) buckets, _, err := csr.NativeBuckets() @@ -213,12 +214,13 @@ func TestCreatePreStates(t *testing.T) { DepositGas: nil, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, ReviseHeights: []uint64{g.GreenlandBlockHeight}}, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) ctx := protocol.WithBlockCtx( genesis.WithGenesisContext(context.Background(), g), @@ -281,13 +283,14 @@ func Test_CreatePreStatesWithRegisterProtocol(t *testing.T) { DepositGas: nil, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, ReviseHeights: []uint64{g.GreenlandBlockHeight}, }, - }, cbi, nil, nil) + }, nil, cbi, nil, nil) require.NoError(err) rol := rolldpos.NewProtocol(23, 4, 3) @@ -405,12 +408,13 @@ func Test_CreateGenesisStates(t *testing.T) { DepositGas: nil, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: cfg, - PersistStakingPatchBlock: math.MaxUint64, + Staking: cfg, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) v, err := p.Start(ctx, sm) @@ -446,12 +450,13 @@ func TestProtocol_ActiveCandidates(t *testing.T) { DepositGas: nil, BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: cfg, - PersistStakingPatchBlock: math.MaxUint64, + Staking: cfg, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, csIndexer, nil) + }, nil, nil, csIndexer, nil) require.NoError(err) blkHeight := g.QuebecBlockHeight + 1 diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index d85b8a38e4..3376db1e1a 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -61,7 +61,7 @@ func TestStakingStateReader(t *testing.T) { StakeStartTime: time.Now(), AutoStake: true, ContractAddress: contractAddress, - UnstakeStartBlockHeight: maxBlockNumber, + UnstakeStartBlockHeight: MaxDurationNumber, }, { Index: 2, @@ -73,7 +73,7 @@ func TestStakingStateReader(t *testing.T) { StakeStartTime: time.Now(), AutoStake: true, ContractAddress: contractAddress, - UnstakeStartBlockHeight: maxBlockNumber, + UnstakeStartBlockHeight: MaxDurationNumber, }, } testNativeBuckets := []*VoteBucket{ diff --git a/action/protocol/staking/validations_test.go b/action/protocol/staking/validations_test.go index daad437d37..bff1d33897 100644 --- a/action/protocol/staking/validations_test.go +++ b/action/protocol/staking/validations_test.go @@ -28,7 +28,7 @@ func initTestProtocol(t *testing.T) (*Protocol, []*Candidate) { Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, }, - }, nil, nil, nil) + }, nil, nil, nil, nil) require.NoError(err) var cans []*Candidate diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index 10f0f9dac5..036d4850a5 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -30,8 +30,8 @@ type ( CreatePreStates(ctx context.Context) error // Handle handles the receipt for the contract stake view Handle(ctx context.Context, receipt *action.Receipt) error - // Migrate migrate the buckets to the state manager - Migrate(protocol.StateManager) error + // Migrate writes the bucket types and buckets to the state manager + Migrate(EventHandler) error // BucketsByCandidate returns the buckets by candidate address BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error @@ -82,10 +82,8 @@ func (v *viewData) Commit(ctx context.Context, sm protocol.StateManager) error { if err := v.bucketPool.Commit(); err != nil { return err } - if v.contractsStake != nil { - if err := v.contractsStake.Commit(ctx, sm); err != nil { - return err - } + if err := v.contractsStake.Commit(ctx, sm); err != nil { + return err } v.snapshots = []Snapshot{} @@ -93,7 +91,7 @@ func (v *viewData) Commit(ctx context.Context, sm protocol.StateManager) error { } func (v *viewData) IsDirty() bool { - return v.candCenter.IsDirty() || v.bucketPool.IsDirty() || (v.contractsStake != nil && v.contractsStake.IsDirty()) + return v.candCenter.IsDirty() || v.bucketPool.IsDirty() || v.contractsStake.IsDirty() } func (v *viewData) Snapshot() int { @@ -128,19 +126,19 @@ func (v *viewData) Revert(snapshot int) error { return nil } -func (csv *contractStakeView) FlushBuckets(sm protocol.StateManager) error { +func (csv *contractStakeView) Migrate(nftHandler EventHandler) error { if csv.v1 != nil { - if err := csv.v1.Migrate(sm); err != nil { + if err := csv.v1.Migrate(nftHandler); err != nil { return err } } if csv.v2 != nil { - if err := csv.v2.Migrate(sm); err != nil { + if err := csv.v2.Migrate(nftHandler); err != nil { return err } } if csv.v3 != nil { - if err := csv.v3.Migrate(sm); err != nil { + if err := csv.v3.Migrate(nftHandler); err != nil { return err } } @@ -201,6 +199,9 @@ func (csv *contractStakeView) CreatePreStates(ctx context.Context) error { } func (csv *contractStakeView) IsDirty() bool { + if csv == nil { + return false + } if csv.v1 != nil && csv.v1.IsDirty() { return true } @@ -214,8 +215,11 @@ func (csv *contractStakeView) IsDirty() bool { } func (csv *contractStakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + if csv == nil { + return nil + } featureCtx, ok := protocol.GetFeatureCtx(ctx) - if !ok || featureCtx.LoadContractStakingFromIndexer { + if !ok || !featureCtx.StoreVoteOfNFTBucketIntoView { sm = nil } if csv.v1 != nil { diff --git a/action/protocol/staking/vote_bucket.go b/action/protocol/staking/vote_bucket.go index 9e11e78a83..40377905c7 100644 --- a/action/protocol/staking/vote_bucket.go +++ b/action/protocol/staking/vote_bucket.go @@ -25,10 +25,6 @@ import ( "github.com/iotexproject/iotex-core/v2/systemcontracts" ) -const ( - maxBlockNumber = math.MaxUint64 -) - type ( // VoteBucket represents a vote VoteBucket struct { @@ -209,7 +205,7 @@ func (vb *VoteBucket) isUnstaked() bool { if vb.isNative() || vb.Timestamped { return vb.UnstakeStartTime.After(vb.StakeStartTime) } - return vb.UnstakeStartBlockHeight < maxBlockNumber + return vb.UnstakeStartBlockHeight < MaxDurationNumber } func (vb *VoteBucket) isNative() bool { diff --git a/action/protocol/staking/vote_bucket_test.go b/action/protocol/staking/vote_bucket_test.go index 52b2a24d42..59e6bcaf52 100644 --- a/action/protocol/staking/vote_bucket_test.go +++ b/action/protocol/staking/vote_bucket_test.go @@ -288,7 +288,7 @@ func TestIsUnstaked(t *testing.T) { vb.ContractAddress = identityset.Address(1).String() vb.CreateBlockHeight = 1 vb.StakeStartBlockHeight = 1 - vb.UnstakeStartBlockHeight = maxBlockNumber + vb.UnstakeStartBlockHeight = MaxDurationNumber r.False(vb.isUnstaked()) vb.UnstakeStartBlockHeight = 2 r.True(vb.isUnstaked()) diff --git a/action/protocol/staking/vote_reviser_test.go b/action/protocol/staking/vote_reviser_test.go index 2dc1307d52..1c1a786081 100644 --- a/action/protocol/staking/vote_reviser_test.go +++ b/action/protocol/staking/vote_reviser_test.go @@ -123,8 +123,9 @@ func TestVoteReviser(t *testing.T) { BlockInterval: getBlockInterval, }, &BuilderConfig{ - Staking: g.Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: g.Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, Revise: ReviseConfig{ VoteWeight: g.Staking.VoteWeightCalConsts, CorrectCandsHeight: g.OkhotskBlockHeight, @@ -134,6 +135,7 @@ func TestVoteReviser(t *testing.T) { nil, nil, nil, + nil, ) r.NotNil(stk) r.NoError(err) @@ -266,6 +268,7 @@ func TestVoteRevise_CorrectEndorsement(t *testing.T) { r.NoError(err) sm.WriteView(_protocolID, view) csm, err := NewCandidateStateManager(sm) + r.NoError(err) esm := NewEndorsementStateManager(sm) // prepare endorsements r.NoError(esm.Put(0, &Endorsement{ExpireHeight: endorsementNotExpireHeight})) diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index fad4b39af4..6f20bb0f4b 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -28,6 +28,7 @@ type ( MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType) BucketType(id uint64) (*BucketType, bool) BucketTypeCount() int + BucketTypes() map[uint64]*BucketType Buckets() ([]uint64, []*BucketType, []*bucketInfo) BucketsByCandidate(candidate address.Address) ([]uint64, []*BucketType, []*bucketInfo) TotalBucketCount() uint64 @@ -116,6 +117,16 @@ func (s *contractStakingCache) MustGetBucketType(id uint64) *BucketType { return s.mustGetBucketType(id) } +func (s *contractStakingCache) BucketTypes() map[uint64]*BucketType { + s.mutex.RLock() + defer s.mutex.RUnlock() + ts := make(map[uint64]*BucketType, len(s.bucketTypeMap)) + for k, v := range s.bucketTypeMap { + ts[k] = v.Clone() + } + return ts +} + func (s *contractStakingCache) BucketType(id uint64) (*BucketType, bool) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -441,13 +452,6 @@ func (s *contractStakingCache) Commit(ctx context.Context, ca address.Address, s s.deltaBuckets = make(map[uint64]*contractstaking.Bucket) return s, nil } - featureCtx, ok := protocol.GetFeatureCtx(ctx) - if !ok { - return s, nil - } - if featureCtx.LoadContractStakingFromIndexer { - return s, nil - } if len(s.deltaBucketTypes) == 0 && len(s.deltaBuckets) == 0 { return s, nil } diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index f2621f4cf1..1144e08e6f 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" "github.com/iotexproject/iotex-core/v2/test/identityset" @@ -62,7 +63,15 @@ func TestContractStakingDirty_getBucketInfo(t *testing.T) { // added bucket info dirty.addBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) - dirty.addBucketInfo(2, &bucketInfo{TypeIndex: 2, CreatedAt: 2, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(2), Owner: identityset.Address(3)}) + dirty.PutBucket(identityset.Address(1), 2, &contractstaking.Bucket{ + StakedAmount: big.NewInt(200), + StakedDuration: 200, + CreatedAt: 2, + UnlockedAt: maxBlockNumber, + UnstakedAt: maxBlockNumber, + Candidate: identityset.Address(2), + Owner: identityset.Address(3), + }) bi, ok = dirty.getBucketInfo(2) require.True(ok) require.EqualValues(2, bi.TypeIndex) @@ -84,7 +93,7 @@ func TestContractStakingDirty_getBucketInfo(t *testing.T) { require.Equal(identityset.Address(4), bi.Owner) // removed bucket info - dirty.deleteBucketInfo(1) + require.NoError(dirty.DeleteBucket(identityset.Address(1), 1)) bi, ok = dirty.getBucketInfo(1) require.False(ok) require.Nil(bi) @@ -240,7 +249,7 @@ func TestContractStakingDirty_noSideEffectOnClean(t *testing.T) { // remove bucket info existed in clean cache clean.PutBucketInfo(3, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) // remove bucket info from dirty cache - dirty.deleteBucketInfo(3) + require.NoError(dirty.DeleteBucket(identityset.Address(5), 3)) // check that clean cache is not affected bi, ok = clean.getBucketInfo(3) require.True(ok) @@ -250,5 +259,4 @@ func TestContractStakingDirty_noSideEffectOnClean(t *testing.T) { require.EqualValues(maxBlockNumber, bi.UnstakedAt) require.EqualValues(identityset.Address(1).String(), bi.Delegate.String()) require.EqualValues(identityset.Address(2).String(), bi.Owner.String()) - } diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index b6fe9c2616..644de9ca5a 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -11,7 +11,6 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/common/math" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" @@ -25,7 +24,7 @@ import ( ) const ( - maxBlockNumber uint64 = math.MaxUint64 + maxBlockNumber uint64 = staking.MaxDurationNumber ) type ( @@ -95,13 +94,12 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { - if !s.IsReady() { - if err := s.start(ctx); err != nil { - return nil, err + if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { + if !s.IsReady() { + if err := s.start(ctx); err != nil { + return nil, err + } } - } - featureCtx, ok := protocol.GetFeatureCtx(ctx) - if !ok || featureCtx.LoadContractStakingFromIndexer { return &stakeView{ contractAddr: s.contractAddr, config: s.config, diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index f9dd38516c..1515c076d4 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -56,9 +56,19 @@ func (s *stakeView) IsDirty() bool { return s.cache.IsDirty() } -func (s *stakeView) Migrate(sm protocol.StateManager) error { +func (s *stakeView) Migrate(handler staking.EventHandler) error { + bts := s.cache.BucketTypes() + tids := make([]uint64, 0, len(bts)) + for id := range bts { + tids = append(tids, id) + } + slices.Sort(tids) + for _, id := range tids { + if err := handler.PutBucketType(s.contractAddr, bts[id]); err != nil { + return err + } + } ids, types, infos := s.cache.Buckets() - cssm := contractstaking.NewContractStakingStateManager(sm) bucketMap := make(map[uint64]*bucketInfo, len(ids)) typeMap := make(map[uint64]*BucketType, len(ids)) for i, id := range ids { @@ -72,7 +82,7 @@ func (s *stakeView) Migrate(sm protocol.StateManager) error { continue } bt := typeMap[id] - if err := cssm.UpsertBucket(s.contractAddr, id, &contractstaking.Bucket{ + if err := handler.PutBucket(s.contractAddr, id, &contractstaking.Bucket{ Candidate: info.Delegate, Owner: info.Owner, StakedAmount: bt.Amount, @@ -86,7 +96,7 @@ func (s *stakeView) Migrate(sm protocol.StateManager) error { return err } } - return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) + return nil } func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { diff --git a/blockindex/contractstaking/wrappedcache.go b/blockindex/contractstaking/wrappedcache.go index 7ce7f15324..8e33bd0e1e 100644 --- a/blockindex/contractstaking/wrappedcache.go +++ b/blockindex/contractstaking/wrappedcache.go @@ -103,6 +103,20 @@ func (wc *wrappedCache) BucketType(id uint64) (*BucketType, bool) { return wc.bucketType(id) } +func (wc *wrappedCache) BucketTypes() map[uint64]*BucketType { + wc.mu.RLock() + defer wc.mu.RUnlock() + types := wc.base.BucketTypes() + for id, bt := range wc.updatedBucketTypes { + if bt != nil { + types[id] = bt.Clone() + } else { + delete(types, id) + } + } + return types +} + func (wc *wrappedCache) bucketType(id uint64) (*BucketType, bool) { bt, ok := wc.updatedBucketTypes[id] if !ok { diff --git a/chainservice/builder.go b/chainservice/builder.go index dfcd8dfc8c..8240e43253 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -342,6 +342,13 @@ func (builder *Builder) buildBlockDAO(forTest bool) error { return nil } +func (builder *Builder) blocksToDurationFn(start uint64, end uint64, viewAt uint64) time.Duration { + if viewAt < builder.cfg.Genesis.WakeBlockHeight { + return time.Duration(end-start) * builder.cfg.DardanellesUpgrade.BlockInterval + } + return time.Duration(end-start) * builder.cfg.WakeUpgrade.BlockInterval +} + func (builder *Builder) buildContractStakingIndexer(forTest bool) error { if !builder.cfg.Chain.EnableStakingProtocol { return nil @@ -352,16 +359,9 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { builder.cs.contractStakingIndexerV3 = nil return nil } - cfg := builder.cfg dbConfig := builder.cfg.DB dbConfig.DbPath = builder.cfg.Chain.ContractStakingIndexDBPath kvstore := db.NewBoltDB(dbConfig) - blockDurationFn := func(start uint64, end uint64, viewAt uint64) time.Duration { - if viewAt < cfg.Genesis.WakeBlockHeight { - return time.Duration(end-start) * cfg.DardanellesUpgrade.BlockInterval - } - return time.Duration(end-start) * cfg.WakeUpgrade.BlockInterval - } // build contract staking indexer if builder.cs.contractStakingIndexer == nil && len(builder.cfg.Genesis.SystemStakingContractAddress) > 0 { voteCalcConsts := builder.cfg.Genesis.VoteWeightCalConsts @@ -373,7 +373,7 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { CalculateVoteWeight: func(v *staking.VoteBucket) *big.Int { return staking.CalculateVoteWeight(voteCalcConsts, v, false) }, - BlocksToDuration: blockDurationFn, + BlocksToDuration: builder.blocksToDurationFn, }) if err != nil { return err @@ -390,7 +390,7 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { kvstore, contractAddr, builder.cfg.Genesis.SystemStakingContractV2Height, - blockDurationFn, + builder.blocksToDurationFn, stakingindex.WithMuteHeight(builder.cfg.Genesis.WakeBlockHeight), ) builder.cs.contractStakingIndexerV2 = indexer @@ -405,7 +405,7 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { kvstore, contractAddr, builder.cfg.Genesis.SystemStakingContractV3Height, - blockDurationFn, + builder.blocksToDurationFn, stakingindex.EnableTimestamped(), ) builder.cs.contractStakingIndexerV3 = indexer @@ -686,10 +686,11 @@ func (builder *Builder) registerStakingProtocol() error { BlockInterval: consensusCfg.BlockInterval, }, &staking.BuilderConfig{ - Staking: builder.cfg.Genesis.Staking, - PersistStakingPatchBlock: builder.cfg.Chain.PersistStakingPatchBlock, - FixAliasForNonStopHeight: builder.cfg.Chain.FixAliasForNonStopHeight, - StakingPatchDir: builder.cfg.Chain.StakingPatchDir, + Staking: builder.cfg.Genesis.Staking, + PersistStakingPatchBlock: builder.cfg.Chain.PersistStakingPatchBlock, + FixAliasForNonStopHeight: builder.cfg.Chain.FixAliasForNonStopHeight, + SkipContractStakingViewHeight: builder.cfg.Genesis.ToBeEnabledBlockHeight, + StakingPatchDir: builder.cfg.Chain.StakingPatchDir, Revise: staking.ReviseConfig{ VoteWeight: builder.cfg.Genesis.VoteWeightCalConsts, ReviseHeights: []uint64{builder.cfg.Genesis.GreenlandBlockHeight, builder.cfg.Genesis.HawaiiBlockHeight}, @@ -698,7 +699,8 @@ func (builder *Builder) registerStakingProtocol() error { CorrectCandSelfStakeHeight: builder.cfg.Genesis.VanuatuBlockHeight, }, }, - nil, + builder.blocksToDurationFn, + builder.cs.candBucketsIndexer, builder.cs.contractStakingIndexer, builder.cs.contractStakingIndexerV2, opts..., diff --git a/go.mod b/go.mod index cb62b3caec..aa23e34054 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 toolchain go1.23.7 require ( - github.com/agiledragon/gomonkey/v2 v2.11.0 + github.com/agiledragon/gomonkey/v2 v2.13.0 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/cenkalti/backoff v2.2.1+incompatible github.com/cespare/xxhash/v2 v2.3.0 diff --git a/go.sum b/go.sum index 26f1469d1c..18d258f211 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9 github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/agiledragon/gomonkey/v2 v2.11.0 h1:5oxSgA+tC1xuGsrIorR+sYiziYltmJyEZ9qA25b6l5U= -github.com/agiledragon/gomonkey/v2 v2.11.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/agiledragon/gomonkey/v2 v2.13.0 h1:B24Jg6wBI1iB8EFR1c+/aoTg7QN/Cum7YffG8KMIyYo= +github.com/agiledragon/gomonkey/v2 v2.13.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0= github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k= diff --git a/state/factory/factory_test.go b/state/factory/factory_test.go index 838d9d728f..eae6cec930 100644 --- a/state/factory/factory_test.go +++ b/state/factory/factory_test.go @@ -1368,12 +1368,14 @@ func TestMintBlocksWithCandidateUpdate(t *testing.T) { }, }, &staking.BuilderConfig{ - Staking: genesis.TestDefault().Staking, - PersistStakingPatchBlock: math.MaxUint64, + Staking: genesis.TestDefault().Staking, + PersistStakingPatchBlock: math.MaxUint64, + SkipContractStakingViewHeight: math.MaxUint64, }, nil, nil, nil, + nil, ) require.NoError(err) require.NoError(sp.Register(registry)) diff --git a/systemcontractindex/stakingindex/eventprocessor.go b/systemcontractindex/stakingindex/eventprocessor.go index 95c7f8a2c9..0ee7cfa3d1 100644 --- a/systemcontractindex/stakingindex/eventprocessor.go +++ b/systemcontractindex/stakingindex/eventprocessor.go @@ -3,7 +3,6 @@ package stakingindex import ( "context" _ "embed" - "math" "strings" "github.com/ethereum/go-ethereum/accounts/abi" @@ -23,7 +22,7 @@ import ( ) const ( - maxStakingNumber uint64 = math.MaxUint64 + maxStakingNumber uint64 = staking.MaxDurationNumber ) var ( diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 74041f01ef..e99c2e62b8 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -134,12 +134,12 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { s.mutex.RLock() defer s.mutex.RUnlock() - if !s.common.Started() { - if err := s.start(ctx); err != nil { - return nil, err + if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { + if !s.common.Started() { + if err := s.start(ctx); err != nil { + return nil, err + } } - } - if protocol.MustGetFeatureCtx(ctx).LoadContractStakingFromIndexer { return &stakeView{ cache: s.cache.Clone(), height: s.common.Height(), diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 5b18dd65fc..0f0fb053e8 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -10,7 +10,6 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type stakeView struct { @@ -54,17 +53,16 @@ func (s *stakeView) IsDirty() bool { return s.cache.IsDirty() } -func (s *stakeView) Migrate(sm protocol.StateManager) error { +func (s *stakeView) Migrate(handler staking.EventHandler) error { ids := s.cache.BucketIdxs() slices.Sort(ids) buckets := s.cache.Buckets(ids) - cssm := contractstaking.NewContractStakingStateManager(sm) for _, id := range ids { - if err := cssm.UpsertBucket(s.contractAddr, id, buckets[id]); err != nil { + if err := handler.PutBucket(s.contractAddr, id, buckets[id]); err != nil { return err } } - return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) + return nil } func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket, error) { From 17e6f5a74c54fd8907b47d16a906bbce6f96e6f9 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 3 Sep 2025 10:40:47 +0800 Subject: [PATCH 18/34] fix pick error --- action/protocol/staking/protocol.go | 1 - chainservice/builder.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index c99cb63cd8..462901e8d8 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -213,7 +213,6 @@ func NewProtocol( MigrateContractAddress: migrateContractAddress, }, blocksToDurationFn: blocksToDurationFn, - candBucketsIndexer: candBucketsIndexer, voteReviser: voteReviser, patch: NewPatchStore(cfg.StakingPatchDir), contractStakingIndexer: contractStakingIndexer, diff --git a/chainservice/builder.go b/chainservice/builder.go index 8240e43253..d9de05b248 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -700,7 +700,7 @@ func (builder *Builder) registerStakingProtocol() error { }, }, builder.blocksToDurationFn, - builder.cs.candBucketsIndexer, + nil, builder.cs.contractStakingIndexer, builder.cs.contractStakingIndexerV2, opts..., From 99c63150501bdd2325989c493ba6a5c158be56de Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 3 Sep 2025 11:44:44 +0800 Subject: [PATCH 19/34] store all contract staking state in erigon --- .../staking/contractstaking/bucket.go | 22 +++++++++- .../staking/contractstaking/bucket_type.go | 23 +++++++++- .../staking/contractstaking/contract.go | 24 ++++++++++- .../staking/contractstaking/statemanager.go | 30 ++++++++----- .../staking/contractstaking/statereader.go | 43 +++++++++++++------ action/protocol/staking/nfteventhandler.go | 17 +++++++- action/protocol/staking/protocol.go | 19 +++++--- systemcontracts/systemcontracts.go | 6 +-- 8 files changed, 150 insertions(+), 34 deletions(-) diff --git a/action/protocol/staking/contractstaking/bucket.go b/action/protocol/staking/contractstaking/bucket.go index 703becee88..b3ef50be22 100644 --- a/action/protocol/staking/contractstaking/bucket.go +++ b/action/protocol/staking/contractstaking/bucket.go @@ -4,9 +4,12 @@ import ( "math/big" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) type ( @@ -35,6 +38,8 @@ type ( } ) +var _ state.ContractStorageProxy = (*Bucket)(nil) + // ErrBucketNotExist is the error when bucket does not exist var ErrBucketNotExist = errors.New("bucket does not exist") @@ -113,3 +118,18 @@ func (b *Bucket) Clone() *Bucket { Muted: b.Muted, } } + +// ContractStorageAddress returns the contract storage address for the bucket. +func (b *Bucket) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + return systemcontracts.SystemContracts[systemcontracts.StakingContractIndex].Address, nil +} + +// New creates a new instance of the bucket. +func (b *Bucket) New() state.ContractStorageStandard { + return &Bucket{} +} + +// ContractStorageProxy returns the contract storage proxy for the bucket. +func (b *Bucket) ContractStorageProxy() state.ContractStorage { + return state.NewContractStorageNamespacedWrapper(b) +} diff --git a/action/protocol/staking/contractstaking/bucket_type.go b/action/protocol/staking/contractstaking/bucket_type.go index 489f4245df..8637189e05 100644 --- a/action/protocol/staking/contractstaking/bucket_type.go +++ b/action/protocol/staking/contractstaking/bucket_type.go @@ -3,9 +3,13 @@ package contractstaking import ( "math/big" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) type ( @@ -17,6 +21,8 @@ type ( } ) +var _ state.ContractStorageProxy = (*BucketType)(nil) + func (bt *BucketType) toProto() *stakingpb.BucketType { return &stakingpb.BucketType{ Amount: bt.Amount.String(), @@ -65,3 +71,18 @@ func (bt *BucketType) Clone() *BucketType { ActivatedAt: bt.ActivatedAt, } } + +// ContractStorageAddress returns the contract storage address for the bucket type +func (bt *BucketType) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + return systemcontracts.SystemContracts[systemcontracts.StakingContractIndex].Address, nil +} + +// New creates a new instance of the bucket type +func (bt *BucketType) New() state.ContractStorageStandard { + return &BucketType{} +} + +// ContractStorageProxy returns the contract storage proxy for the bucket type +func (bt *BucketType) ContractStorageProxy() state.ContractStorage { + return state.NewContractStorageNamespacedWrapper(bt) +} diff --git a/action/protocol/staking/contractstaking/contract.go b/action/protocol/staking/contractstaking/contract.go index 81bfb1ac60..4c8bbaf53a 100644 --- a/action/protocol/staking/contractstaking/contract.go +++ b/action/protocol/staking/contractstaking/contract.go @@ -1,9 +1,14 @@ package contractstaking import ( - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" "github.com/pkg/errors" "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // StakingContract represents the staking contract in the system @@ -12,6 +17,8 @@ type StakingContract struct { NumOfBuckets uint64 } +var _ state.ContractStorageProxy = (*StakingContract)(nil) + func (sc *StakingContract) toProto() *stakingpb.SystemStakingContract { if sc == nil { return nil @@ -50,3 +57,18 @@ func (sc *StakingContract) Deserialize(b []byte) error { *sc = *loaded return nil } + +// ContractStorageAddress returns the address of the contract storage +func (sc *StakingContract) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + return systemcontracts.SystemContracts[systemcontracts.StakingContractIndex].Address, nil +} + +// New creates a new instance of the staking contract +func (sc *StakingContract) New() state.ContractStorageStandard { + return &StakingContract{} +} + +// ContractStorageProxy returns the contract storage proxy +func (sc *StakingContract) ContractStorageProxy() state.ContractStorage { + return state.NewContractStorageNamespacedWrapper(sc) +} diff --git a/action/protocol/staking/contractstaking/statemanager.go b/action/protocol/staking/contractstaking/statemanager.go index 42964cb90e..11221f83db 100644 --- a/action/protocol/staking/contractstaking/statemanager.go +++ b/action/protocol/staking/contractstaking/statemanager.go @@ -2,6 +2,7 @@ package contractstaking import ( "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) @@ -12,9 +13,9 @@ type ContractStakingStateManager struct { } // NewContractStakingStateManager creates a new ContractStakingStateManager -func NewContractStakingStateManager(sm protocol.StateManager) *ContractStakingStateManager { +func NewContractStakingStateManager(sm protocol.StateManager, opts ...protocol.StateOption) *ContractStakingStateManager { return &ContractStakingStateManager{ - ContractStakingStateReader: ContractStakingStateReader{sr: sm}, + ContractStakingStateReader: *NewStateReader(sm, opts...), sm: sm, } } @@ -23,8 +24,10 @@ func NewContractStakingStateManager(sm protocol.StateManager) *ContractStakingSt func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Address, bucketID uint64, bucketType *BucketType) error { _, err := cs.sm.PutState( bucketType, - bucketTypeNamespaceOption(contractAddr), - bucketIDKeyOption(bucketID), + cs.makeOpts( + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + )..., ) return err @@ -33,8 +36,11 @@ func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Add // DeleteBucket removes a bucket for a given contract and bucket ID. func (cs *ContractStakingStateManager) DeleteBucket(contractAddr address.Address, bucketID uint64) error { _, err := cs.sm.DelState( - bucketTypeNamespaceOption(contractAddr), - bucketIDKeyOption(bucketID), + cs.makeOpts( + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + protocol.ObjectOption(&Bucket{}), + )..., ) return err @@ -44,8 +50,10 @@ func (cs *ContractStakingStateManager) DeleteBucket(contractAddr address.Address func (cs *ContractStakingStateManager) UpsertBucket(contractAddr address.Address, bid uint64, bucket *Bucket) error { _, err := cs.sm.PutState( bucket, - contractNamespaceOption(contractAddr), - bucketIDKeyOption(bid), + cs.makeOpts( + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bid), + )..., ) return err @@ -57,8 +65,10 @@ func (cs *ContractStakingStateManager) UpdateNumOfBuckets(contractAddr address.A &StakingContract{ NumOfBuckets: uint64(numOfBuckets), }, - metaNamespaceOption(), - contractKeyOption(contractAddr), + cs.makeOpts( + metaNamespaceOption(), + contractKeyOption(contractAddr), + )..., ) return err diff --git a/action/protocol/staking/contractstaking/statereader.go b/action/protocol/staking/contractstaking/statereader.go index 98b0d542d2..f6a83ef4fc 100644 --- a/action/protocol/staking/contractstaking/statereader.go +++ b/action/protocol/staking/contractstaking/statereader.go @@ -3,24 +3,27 @@ package contractstaking import ( "fmt" + "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" "github.com/iotexproject/iotex-core/v2/state" - "github.com/pkg/errors" "github.com/iotexproject/iotex-address/address" ) // ContractStakingStateReader wraps a state reader to provide staking contract-specific reads. type ContractStakingStateReader struct { - sr protocol.StateReader + sr protocol.StateReader + globalOpts []protocol.StateOption } // NewStateReader creates a new ContractStakingStateReader. -func NewStateReader(sr protocol.StateReader) *ContractStakingStateReader { +func NewStateReader(sr protocol.StateReader, opts ...protocol.StateOption) *ContractStakingStateReader { return &ContractStakingStateReader{ - sr: sr, + sr: sr, + globalOpts: opts, } } @@ -49,8 +52,10 @@ func (r *ContractStakingStateReader) contract(contractAddr address.Address) (*St var contract StakingContract _, err := r.sr.State( &contract, - metaNamespaceOption(), - contractKeyOption(contractAddr), + r.makeOpts( + metaNamespaceOption(), + contractKeyOption(contractAddr), + )..., ) if err != nil { return nil, err @@ -72,8 +77,10 @@ func (r *ContractStakingStateReader) BucketType(contractAddr address.Address, tI var bktType stakingpb.BucketType if _, err := r.sr.State( &bktType, - bucketTypeNamespaceOption(contractAddr), - bucketIDKeyOption(tID), + r.makeOpts( + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(tID), + )..., ); err != nil { return nil, fmt.Errorf("failed to get bucket type %d for contract %s: %w", tID, contractAddr.String(), err) } @@ -85,8 +92,10 @@ func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucket var ssb Bucket if _, err := r.sr.State( &ssb, - contractNamespaceOption(contractAddr), - bucketIDKeyOption(bucketID), + r.makeOpts( + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + )..., ); err != nil { switch errors.Cause(err) { case state.ErrStateNotExist: @@ -100,7 +109,10 @@ func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucket // BucketTypes returns all BucketType for a given contract and bucket id. func (r *ContractStakingStateReader) BucketTypes(contractAddr address.Address) ([]uint64, []*BucketType, error) { - _, iter, err := r.sr.States(bucketTypeNamespaceOption(contractAddr)) + _, iter, err := r.sr.States(r.makeOpts( + bucketTypeNamespaceOption(contractAddr), + protocol.ObjectOption(&BucketType{}), + )...) if err != nil { return nil, nil, fmt.Errorf("failed to get bucket types for contract %s: %w", contractAddr.String(), err) } @@ -126,7 +138,10 @@ func (r *ContractStakingStateReader) BucketTypes(contractAddr address.Address) ( // Buckets returns all BucketInfo for a given contract. func (r *ContractStakingStateReader) Buckets(contractAddr address.Address) ([]uint64, []*Bucket, error) { - _, iter, err := r.sr.States(contractNamespaceOption(contractAddr)) + _, iter, err := r.sr.States(r.makeOpts( + contractNamespaceOption(contractAddr), + protocol.ObjectOption(&Bucket{}), + )...) if err != nil { return nil, nil, fmt.Errorf("failed to get buckets for contract %s: %w", contractAddr.String(), err) } @@ -151,3 +166,7 @@ func (r *ContractStakingStateReader) Buckets(contractAddr address.Address) ([]ui } return ids, buckets, nil } + +func (cs *ContractStakingStateReader) makeOpts(opts ...protocol.StateOption) []protocol.StateOption { + return append(cs.globalOpts, opts...) +} diff --git a/action/protocol/staking/nfteventhandler.go b/action/protocol/staking/nfteventhandler.go index 009fe90594..a403d1f085 100644 --- a/action/protocol/staking/nfteventhandler.go +++ b/action/protocol/staking/nfteventhandler.go @@ -4,9 +4,10 @@ import ( "math/big" "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" - "github.com/pkg/errors" ) type ( @@ -36,6 +37,20 @@ func newNFTBucketEventHandler(sm protocol.StateManager, calculateVoteWeight Calc }, nil } +func newNFTBucketEventHandlerSecondaryOnly(sm protocol.StateManager, calculateVoteWeight CalculateVoteWeightFunc) (*nftEventHandler, error) { + csm, err := NewCandidateStateManager(sm) + if err != nil { + return nil, err + } + return &nftEventHandler{ + calculateVoteWeight: calculateVoteWeight, + cssm: contractstaking.NewContractStakingStateManager(sm, protocol.SecondaryOnlyOption()), + csm: csm, + bucketTypes: make(map[address.Address]map[uint64]*contractstaking.BucketType), + bucketTypesLookup: make(map[address.Address]map[int64]map[uint64]uint64), + }, nil +} + func (handler *nftEventHandler) matchBucketType(contractAddr address.Address, amount *big.Int, duration uint64) (uint64, error) { cmap, ok := handler.bucketTypesLookup[contractAddr] if !ok { diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 462901e8d8..fbfcec9501 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -590,17 +590,26 @@ func (p *Protocol) HandleReceipt(ctx context.Context, elp action.Envelope, sm pr if !ok { return errors.New("failed to get feature context from action context") } + var ( + handler EventHandler + err error + voteCalcFn = func(bucket *contractstaking.Bucket, height uint64) *big.Int { + vb := p.convertToVoteBucket(bucket, height) + return p.calculateVoteWeight(vb, false) + } + ) if featureCtx.StoreVoteOfNFTBucketIntoView { v, err := sm.ReadView(_protocolID) if err != nil { return err } - return v.(*viewData).contractsStake.Handle(ctx, receipt) + if err = v.(*viewData).contractsStake.Handle(ctx, receipt); err != nil { + return err + } + handler, err = newNFTBucketEventHandlerSecondaryOnly(sm, voteCalcFn) + } else { + handler, err = newNFTBucketEventHandler(sm, voteCalcFn) } - handler, err := newNFTBucketEventHandler(sm, func(bucket *contractstaking.Bucket, height uint64) *big.Int { - vb := p.convertToVoteBucket(bucket, height) - return p.calculateVoteWeight(vb, false) - }) if err != nil { return err } diff --git a/systemcontracts/systemcontracts.go b/systemcontracts/systemcontracts.go index df83219409..cf5328374a 100644 --- a/systemcontracts/systemcontracts.go +++ b/systemcontracts/systemcontracts.go @@ -48,8 +48,8 @@ const ( RewardingContractV1Index // RewardingContractV2Index is the system contract for rewarding admin storage v2 RewardingContractV2Index - // StakingViewContractIndex is the system contract for staking view storage - StakingViewContractIndex + // StakingContractIndex is the system contract for staking view storage + StakingContractIndex // SystemContractCount is the total number of system contracts SystemContractCount ) @@ -60,7 +60,7 @@ const ( ) var systemContractTypes = map[int]int{ - StakingViewContractIndex: namespaceStorageContractType, + StakingContractIndex: namespaceStorageContractType, } // SystemContracts holds all system contracts From 9f16b201fb190cfc3673b1eaacac24a9d2aa17f2 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 4 Sep 2025 05:34:07 +0800 Subject: [PATCH 20/34] resume historical views --- action/protocol/protocol.go | 5 + action/protocol/registry.go | 25 ++ .../staking/contractstaking/contract.go | 4 + .../staking/contractstaking/statemanager.go | 7 +- .../staking/contractstaking/statereader.go | 9 + action/protocol/staking/protocol.go | 5 + action/protocol/staking/stakeview_builder.go | 13 +- .../protocol/staking/stakingpb/staking.pb.go | 356 +++++++++--------- .../protocol/staking/stakingpb/staking.proto | 1 + action/protocol/staking/viewdata.go | 5 +- blockindex/contractstaking/indexer.go | 12 +- blockindex/contractstaking/stakeview.go | 4 + state/factory/statedb.go | 10 + systemcontractindex/stakingindex/index.go | 14 +- systemcontractindex/stakingindex/stakeview.go | 4 + 15 files changed, 286 insertions(+), 188 deletions(-) diff --git a/action/protocol/protocol.go b/action/protocol/protocol.go index f2c96bf070..58ed1e3738 100644 --- a/action/protocol/protocol.go +++ b/action/protocol/protocol.go @@ -45,6 +45,11 @@ type Starter interface { Start(context.Context, StateReader) (View, error) } +// Viewer defines the viewer interface for the protocol +type Viewer interface { + ViewAt(context.Context, StateReader) (View, error) +} + // GenesisStateCreator creates some genesis states type GenesisStateCreator interface { CreateGenesisStates(context.Context, StateManager) error diff --git a/action/protocol/registry.go b/action/protocol/registry.go index 13faf6a835..f2ee449e20 100644 --- a/action/protocol/registry.go +++ b/action/protocol/registry.go @@ -113,3 +113,28 @@ func (r *Registry) StartAll(ctx context.Context, sr StateReader) (*Views, error) } return allViews, nil } + +// ViewsAt returns the views of all protocols at a specific state +func (r *Registry) ViewsAt(ctx context.Context, sr StateReader) (*Views, error) { + if r == nil { + return nil, nil + } + r.mu.RLock() + defer r.mu.RUnlock() + allViews := NewViews() + for _, p := range r.all() { + s, ok := p.(Viewer) + if !ok { + continue + } + view, err := s.ViewAt(ctx, sr) + if err != nil { + return nil, errors.Wrapf(err, "failed to start protocol %s", reflect.TypeOf(p)) + } + if view == nil { + continue + } + allViews.Write(p.Name(), view) + } + return allViews, nil +} diff --git a/action/protocol/staking/contractstaking/contract.go b/action/protocol/staking/contractstaking/contract.go index 4c8bbaf53a..92f6654d07 100644 --- a/action/protocol/staking/contractstaking/contract.go +++ b/action/protocol/staking/contractstaking/contract.go @@ -15,6 +15,8 @@ import ( type StakingContract struct { // NumOfBuckets is the number of buckets in the staking contract NumOfBuckets uint64 + // Height is the height of the staking contract + Height uint64 } var _ state.ContractStorageProxy = (*StakingContract)(nil) @@ -25,6 +27,7 @@ func (sc *StakingContract) toProto() *stakingpb.SystemStakingContract { } return &stakingpb.SystemStakingContract{ NumOfBuckets: sc.NumOfBuckets, + Height: sc.Height, } } @@ -35,6 +38,7 @@ func LoadStakingContractFromProto(pb *stakingpb.SystemStakingContract) (*Staking } sc := &StakingContract{ NumOfBuckets: pb.NumOfBuckets, + Height: pb.Height, } return sc, nil } diff --git a/action/protocol/staking/contractstaking/statemanager.go b/action/protocol/staking/contractstaking/statemanager.go index 11221f83db..af68d0ee92 100644 --- a/action/protocol/staking/contractstaking/statemanager.go +++ b/action/protocol/staking/contractstaking/statemanager.go @@ -61,9 +61,14 @@ func (cs *ContractStakingStateManager) UpsertBucket(contractAddr address.Address // UpdateNumOfBuckets updates the number of buckets. func (cs *ContractStakingStateManager) UpdateNumOfBuckets(contractAddr address.Address, numOfBuckets uint64) error { - _, err := cs.sm.PutState( + height, err := cs.sm.Height() + if err != nil { + return err + } + _, err = cs.sm.PutState( &StakingContract{ NumOfBuckets: uint64(numOfBuckets), + Height: height, }, cs.makeOpts( metaNamespaceOption(), diff --git a/action/protocol/staking/contractstaking/statereader.go b/action/protocol/staking/contractstaking/statereader.go index f6a83ef4fc..98e4daf305 100644 --- a/action/protocol/staking/contractstaking/statereader.go +++ b/action/protocol/staking/contractstaking/statereader.go @@ -72,6 +72,15 @@ func (r *ContractStakingStateReader) NumOfBuckets(contractAddr address.Address) return contract.NumOfBuckets, nil } +// Height returns the height of the staking contract. +func (r *ContractStakingStateReader) Height(contractAddr address.Address) (uint64, error) { + contract, err := r.contract(contractAddr) + if err != nil { + return 0, err + } + return contract.Height, nil +} + // BucketType returns the BucketType for a given contract and bucket id. func (r *ContractStakingStateReader) BucketType(contractAddr address.Address, tID uint64) (*BucketType, error) { var bktType stakingpb.BucketType diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index fbfcec9501..435607082c 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -232,6 +232,11 @@ func ProtocolAddr() address.Address { // Start starts the protocol func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol.View, error) { + return p.ViewsAt(ctx, sr) +} + +// ViewsAt returns the view at a specific height +func (p *Protocol) ViewsAt(ctx context.Context, sr protocol.StateReader) (protocol.View, error) { featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx) height, err := sr.Height() if err != nil { diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go index 1dc1bedcf7..12a6252d7b 100644 --- a/action/protocol/staking/stakeview_builder.go +++ b/action/protocol/staking/stakeview_builder.go @@ -37,20 +37,17 @@ func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateR if err != nil { return nil, err } - indexerHeight, err := b.indexer.Height() - if err != nil { - return nil, err - } - if indexerHeight == height { + viewHeight := view.Height() + if viewHeight == height { return view, nil } - if indexerHeight > height { - return nil, errors.Errorf("indexer height %d is greater than requested height %d", indexerHeight, height) + if viewHeight > height { + return nil, errors.Errorf("indexer height %d is greater than requested height %d", viewHeight, height) } if b.blockdao == nil { return nil, errors.Errorf("blockdao is nil, cannot build view for height %d", height) } - for h := indexerHeight + 1; h <= height; h++ { + for h := viewHeight + 1; h <= height; h++ { receipts, err := b.blockdao.GetReceipts(h) if err != nil { return nil, errors.Wrapf(err, "failed to get receipts at height %d", h) diff --git a/action/protocol/staking/stakingpb/staking.pb.go b/action/protocol/staking/stakingpb/staking.pb.go index 665c795614..ca9619eca9 100644 --- a/action/protocol/staking/stakingpb/staking.pb.go +++ b/action/protocol/staking/stakingpb/staking.pb.go @@ -8,9 +8,9 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.3 -// source: staking.proto +// protoc-gen-go v1.26.0 +// protoc v4.23.3 +// source: action/protocol/staking/stakingpb/staking.proto package stakingpb @@ -53,7 +53,7 @@ type Bucket struct { func (x *Bucket) Reset() { *x = Bucket{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[0] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -66,7 +66,7 @@ func (x *Bucket) String() string { func (*Bucket) ProtoMessage() {} func (x *Bucket) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[0] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -79,7 +79,7 @@ func (x *Bucket) ProtoReflect() protoreflect.Message { // Deprecated: Use Bucket.ProtoReflect.Descriptor instead. func (*Bucket) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{0} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{0} } func (x *Bucket) GetIndex() uint64 { @@ -191,7 +191,7 @@ type BucketIndices struct { func (x *BucketIndices) Reset() { *x = BucketIndices{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[1] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -204,7 +204,7 @@ func (x *BucketIndices) String() string { func (*BucketIndices) ProtoMessage() {} func (x *BucketIndices) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[1] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -217,7 +217,7 @@ func (x *BucketIndices) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketIndices.ProtoReflect.Descriptor instead. func (*BucketIndices) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{1} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{1} } func (x *BucketIndices) GetIndices() []uint64 { @@ -246,7 +246,7 @@ type Candidate struct { func (x *Candidate) Reset() { *x = Candidate{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[2] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -259,7 +259,7 @@ func (x *Candidate) String() string { func (*Candidate) ProtoMessage() {} func (x *Candidate) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[2] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -272,7 +272,7 @@ func (x *Candidate) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidate.ProtoReflect.Descriptor instead. func (*Candidate) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{2} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{2} } func (x *Candidate) GetOwnerAddress() string { @@ -349,7 +349,7 @@ type Candidates struct { func (x *Candidates) Reset() { *x = Candidates{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[3] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -362,7 +362,7 @@ func (x *Candidates) String() string { func (*Candidates) ProtoMessage() {} func (x *Candidates) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[3] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -375,7 +375,7 @@ func (x *Candidates) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidates.ProtoReflect.Descriptor instead. func (*Candidates) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{3} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{3} } func (x *Candidates) GetCandidates() []*Candidate { @@ -397,7 +397,7 @@ type TotalAmount struct { func (x *TotalAmount) Reset() { *x = TotalAmount{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[4] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -410,7 +410,7 @@ func (x *TotalAmount) String() string { func (*TotalAmount) ProtoMessage() {} func (x *TotalAmount) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[4] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -423,7 +423,7 @@ func (x *TotalAmount) ProtoReflect() protoreflect.Message { // Deprecated: Use TotalAmount.ProtoReflect.Descriptor instead. func (*TotalAmount) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{4} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{4} } func (x *TotalAmount) GetAmount() string { @@ -453,7 +453,7 @@ type BucketType struct { func (x *BucketType) Reset() { *x = BucketType{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[5] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +466,7 @@ func (x *BucketType) String() string { func (*BucketType) ProtoMessage() {} func (x *BucketType) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[5] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +479,7 @@ func (x *BucketType) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketType.ProtoReflect.Descriptor instead. func (*BucketType) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{5} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{5} } func (x *BucketType) GetAmount() string { @@ -514,7 +514,7 @@ type Endorsement struct { func (x *Endorsement) Reset() { *x = Endorsement{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[6] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -527,7 +527,7 @@ func (x *Endorsement) String() string { func (*Endorsement) ProtoMessage() {} func (x *Endorsement) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[6] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -540,7 +540,7 @@ func (x *Endorsement) ProtoReflect() protoreflect.Message { // Deprecated: Use Endorsement.ProtoReflect.Descriptor instead. func (*Endorsement) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{6} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{6} } func (x *Endorsement) GetExpireHeight() uint64 { @@ -556,12 +556,13 @@ type SystemStakingContract struct { unknownFields protoimpl.UnknownFields NumOfBuckets uint64 `protobuf:"varint,1,opt,name=numOfBuckets,proto3" json:"numOfBuckets,omitempty"` + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` } func (x *SystemStakingContract) Reset() { *x = SystemStakingContract{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[7] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -574,7 +575,7 @@ func (x *SystemStakingContract) String() string { func (*SystemStakingContract) ProtoMessage() {} func (x *SystemStakingContract) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[7] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -587,7 +588,7 @@ func (x *SystemStakingContract) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemStakingContract.ProtoReflect.Descriptor instead. func (*SystemStakingContract) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{7} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{7} } func (x *SystemStakingContract) GetNumOfBuckets() uint64 { @@ -597,6 +598,13 @@ func (x *SystemStakingContract) GetNumOfBuckets() uint64 { return 0 } +func (x *SystemStakingContract) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + type SystemStakingBucket struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -615,7 +623,7 @@ type SystemStakingBucket struct { func (x *SystemStakingBucket) Reset() { *x = SystemStakingBucket{} if protoimpl.UnsafeEnabled { - mi := &file_staking_proto_msgTypes[8] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -628,7 +636,7 @@ func (x *SystemStakingBucket) String() string { func (*SystemStakingBucket) ProtoMessage() {} func (x *SystemStakingBucket) ProtoReflect() protoreflect.Message { - mi := &file_staking_proto_msgTypes[8] + mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -641,7 +649,7 @@ func (x *SystemStakingBucket) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemStakingBucket.ProtoReflect.Descriptor instead. func (*SystemStakingBucket) Descriptor() ([]byte, []int) { - return file_staking_proto_rawDescGZIP(), []int{8} + return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{8} } func (x *SystemStakingBucket) GetOwner() []byte { @@ -700,135 +708,139 @@ func (x *SystemStakingBucket) GetMuted() bool { return false } -var File_staking_proto protoreflect.FileDescriptor - -var file_staking_proto_rawDesc = []byte{ - 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, 0x0a, 0x06, - 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x10, - 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x74, - 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, - 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x74, - 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x73, - 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x17, - 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x75, - 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, - 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, - 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, - 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, - 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x2e, 0x0a, - 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, - 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, - 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, 0x1c, 0x0a, - 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, - 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x2e, - 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x73, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3b, 0x0a, 0x15, 0x53, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, 0x75, 0x63, 0x6b, 0x65, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, - 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x22, 0xf1, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6f, - 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, - 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, - 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +var File_action_protocol_staking_stakingpb_staking_proto protoreflect.FileDescriptor + +var file_action_protocol_staking_stakingpb_staking_proto_rawDesc = []byte{ + 0x0a, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, + 0x67, 0x70, 0x62, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, + 0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, + 0x0a, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, + 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x19, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, + 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, + 0x65, 0x74, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, + 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, + 0x63, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, + 0x2e, 0x0a, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, + 0x65, 0x74, 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, + 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, + 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, + 0x62, 0x2e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, + 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, + 0x72, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x53, 0x0a, 0x15, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, 0x75, 0x63, + 0x6b, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, + 0x66, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x22, 0xf1, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, + 0x6e, 0x67, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, + 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, + 0x75, 0x74, 0x65, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, + 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, + 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( - file_staking_proto_rawDescOnce sync.Once - file_staking_proto_rawDescData = file_staking_proto_rawDesc + file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce sync.Once + file_action_protocol_staking_stakingpb_staking_proto_rawDescData = file_action_protocol_staking_stakingpb_staking_proto_rawDesc ) -func file_staking_proto_rawDescGZIP() []byte { - file_staking_proto_rawDescOnce.Do(func() { - file_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_staking_proto_rawDescData) +func file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP() []byte { + file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce.Do(func() { + file_action_protocol_staking_stakingpb_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_action_protocol_staking_stakingpb_staking_proto_rawDescData) }) - return file_staking_proto_rawDescData + return file_action_protocol_staking_stakingpb_staking_proto_rawDescData } -var file_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 9) -var file_staking_proto_goTypes = []any{ +var file_action_protocol_staking_stakingpb_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_action_protocol_staking_stakingpb_staking_proto_goTypes = []interface{}{ (*Bucket)(nil), // 0: stakingpb.Bucket (*BucketIndices)(nil), // 1: stakingpb.BucketIndices (*Candidate)(nil), // 2: stakingpb.Candidate @@ -840,7 +852,7 @@ var file_staking_proto_goTypes = []any{ (*SystemStakingBucket)(nil), // 8: stakingpb.SystemStakingBucket (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } -var file_staking_proto_depIdxs = []int32{ +var file_action_protocol_staking_stakingpb_staking_proto_depIdxs = []int32{ 9, // 0: stakingpb.Bucket.createTime:type_name -> google.protobuf.Timestamp 9, // 1: stakingpb.Bucket.stakeStartTime:type_name -> google.protobuf.Timestamp 9, // 2: stakingpb.Bucket.unstakeStartTime:type_name -> google.protobuf.Timestamp @@ -852,13 +864,13 @@ var file_staking_proto_depIdxs = []int32{ 0, // [0:4] is the sub-list for field type_name } -func init() { file_staking_proto_init() } -func file_staking_proto_init() { - if File_staking_proto != nil { +func init() { file_action_protocol_staking_stakingpb_staking_proto_init() } +func file_action_protocol_staking_stakingpb_staking_proto_init() { + if File_action_protocol_staking_stakingpb_staking_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_staking_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Bucket); i { case 0: return &v.state @@ -870,7 +882,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BucketIndices); i { case 0: return &v.state @@ -882,7 +894,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Candidate); i { case 0: return &v.state @@ -894,7 +906,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Candidates); i { case 0: return &v.state @@ -906,7 +918,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TotalAmount); i { case 0: return &v.state @@ -918,7 +930,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BucketType); i { case 0: return &v.state @@ -930,7 +942,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Endorsement); i { case 0: return &v.state @@ -942,7 +954,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SystemStakingContract); i { case 0: return &v.state @@ -954,7 +966,7 @@ func file_staking_proto_init() { return nil } } - file_staking_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_action_protocol_staking_stakingpb_staking_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SystemStakingBucket); i { case 0: return &v.state @@ -971,18 +983,18 @@ func file_staking_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_staking_proto_rawDesc, + RawDescriptor: file_action_protocol_staking_stakingpb_staking_proto_rawDesc, NumEnums: 0, NumMessages: 9, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_staking_proto_goTypes, - DependencyIndexes: file_staking_proto_depIdxs, - MessageInfos: file_staking_proto_msgTypes, + GoTypes: file_action_protocol_staking_stakingpb_staking_proto_goTypes, + DependencyIndexes: file_action_protocol_staking_stakingpb_staking_proto_depIdxs, + MessageInfos: file_action_protocol_staking_stakingpb_staking_proto_msgTypes, }.Build() - File_staking_proto = out.File - file_staking_proto_rawDesc = nil - file_staking_proto_goTypes = nil - file_staking_proto_depIdxs = nil + File_action_protocol_staking_stakingpb_staking_proto = out.File + file_action_protocol_staking_stakingpb_staking_proto_rawDesc = nil + file_action_protocol_staking_stakingpb_staking_proto_goTypes = nil + file_action_protocol_staking_stakingpb_staking_proto_depIdxs = nil } diff --git a/action/protocol/staking/stakingpb/staking.proto b/action/protocol/staking/stakingpb/staking.proto index 236279f1c9..3e901caa07 100644 --- a/action/protocol/staking/stakingpb/staking.proto +++ b/action/protocol/staking/stakingpb/staking.proto @@ -65,6 +65,7 @@ message Endorsement { message SystemStakingContract { uint64 numOfBuckets = 1; + uint64 height = 2; } message SystemStakingBucket { diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index 036d4850a5..ca21ef6b51 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -10,9 +10,10 @@ import ( "math/big" "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/pkg/errors" ) type ( @@ -35,6 +36,8 @@ type ( // BucketsByCandidate returns the buckets by candidate address BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error + // Height returns the height of the contract stake view + Height() uint64 } // viewData is the data that need to be stored in protocol's view viewData struct { diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 644de9ca5a..00be8ec797 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -94,7 +94,13 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { - if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { + cssr := contractstaking.NewStateReader(sr) + cssrHeight, err := cssr.Height(s.contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get height for contract %s", s.contractAddr) + } + // contract staking state have not been initialized in state reader, we need to read from index + if cssrHeight == 0 { if !s.IsReady() { if err := s.start(ctx); err != nil { return nil, err @@ -108,7 +114,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s genBlockDurationFn: s.genBlockDurationFn, }, nil } - cssr := contractstaking.NewStateReader(sr) + // otherwise, we need to read from state reader tids, types, err := cssr.BucketTypes(s.contractAddr) if err != nil { return nil, errors.Wrapf(err, "failed to get bucket types for contract %s", s.contractAddr) @@ -155,7 +161,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s return &stakeView{ cache: cache, - height: s.height, + height: cssrHeight, config: s.config, contractAddr: s.contractAddr, }, nil diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index 1515c076d4..eaaa68f275 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -21,6 +21,10 @@ type stakeView struct { height uint64 } +func (s *stakeView) Height() uint64 { + return s.height +} + func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ contractAddr: s.contractAddr, diff --git a/state/factory/statedb.go b/state/factory/statedb.go index de98e315fe..dc94384540 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -371,6 +371,11 @@ func (sdb *stateDB) WorkingSetAtTransaction(ctx context.Context, height uint64, return nil, err } ws.store = newErigonWorkingSetStoreForSimulate(ws.store, e) + views, err := sdb.registry.ViewsAt(ctx, ws) + if err != nil { + return nil, err + } + ws.views = views } // handle panic to ensure workingset is closed defer func() { @@ -411,6 +416,11 @@ func (sdb *stateDB) WorkingSetAtHeight(ctx context.Context, height uint64) (prot return nil, err } ws.store = newErigonWorkingSetStoreForSimulate(ws.store, e) + views, err := sdb.registry.ViewsAt(ctx, ws) + if err != nil { + return nil, err + } + ws.views = views } return ws, nil } diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index e99c2e62b8..96fbaa6911 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -134,7 +134,15 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { s.mutex.RLock() defer s.mutex.RUnlock() - if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { + + contractAddr := s.common.ContractAddress() + csr := contractstaking.NewStateReader(sr) + csrHeight, err := csr.Height(contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get height for contract %s", contractAddr) + } + // contract staking state have not been initialized in state reader, we need to read from index + if csrHeight == 0 { if !s.common.Started() { if err := s.start(ctx); err != nil { return nil, err @@ -151,7 +159,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s genBlockDurationFn: s.genBlockDurationFn, }, nil } - contractAddr := s.common.ContractAddress() + // otherwise, we need to read from state reader ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(contractAddr) if err != nil { return nil, errors.Wrapf(err, "failed to get buckets for contract %s", contractAddr) @@ -169,7 +177,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s } return &stakeView{ cache: cache, - height: s.common.Height(), + height: csrHeight, contractAddr: s.common.ContractAddress(), muteHeight: s.muteHeight, startHeight: s.common.StartHeight(), diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 0f0fb053e8..fac3a7c55c 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -23,6 +23,10 @@ type stakeView struct { genBlockDurationFn func(view uint64) blocksDurationFn } +func (s *stakeView) Height() uint64 { + return s.height +} + func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ cache: newWrappedCache(s.cache), From 5afb1fe0a20774b8b3221c93078f7cfd9879477b Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 4 Sep 2025 19:51:58 +0800 Subject: [PATCH 21/34] draft: memory candidate votes instead of buckets --- .../protocol/staking/contractstake_indexer.go | 3 + action/protocol/staking/protocol.go | 76 ++++--- action/protocol/staking/stakeview_builder.go | 3 +- action/protocol/staking/viewdata.go | 7 +- blockindex/contractstaking/indexer.go | 4 + blockindex/contractstaking/stakeview.go | 7 +- blockindex/contractstaking/voteview.go | 1 + .../stakingindex/candidate_votes.go | 204 ++++++++++++++++++ .../stakingindex/event_handler.go | 1 + systemcontractindex/stakingindex/index.go | 101 +++++---- systemcontractindex/stakingindex/stakeview.go | 130 ----------- .../stakingindex/stakingpb/staking.pb.go | 176 +++++++++++++-- .../stakingindex/stakingpb/staking.proto | 10 + .../stakingindex/vote_view_handler.go | 128 +++++++++++ systemcontractindex/stakingindex/voteview.go | 179 +++++++++++++++ 15 files changed, 814 insertions(+), 216 deletions(-) create mode 100644 blockindex/contractstaking/voteview.go create mode 100644 systemcontractindex/stakingindex/candidate_votes.go delete mode 100644 systemcontractindex/stakingindex/stakeview.go create mode 100644 systemcontractindex/stakingindex/vote_view_handler.go create mode 100644 systemcontractindex/stakingindex/voteview.go diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index ebe0b1952f..4ec8ed1c51 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" @@ -56,6 +57,8 @@ type ( LoadStakeView(context.Context, protocol.StateReader) (ContractStakeView, error) // CreateEventProcessor creates a new event processor CreateEventProcessor(context.Context, EventHandler) EventProcessor + // CreateMemoryEventHandler creates a new memory event handler + CreateMemoryEventHandler(context.Context) EventHandler } // ContractStakingIndexerWithBucketType defines the interface of contract staking reader with bucket type ContractStakingIndexerWithBucketType interface { diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 435607082c..590bd286eb 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -232,11 +232,11 @@ func ProtocolAddr() address.Address { // Start starts the protocol func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol.View, error) { - return p.ViewsAt(ctx, sr) + return p.ViewAt(ctx, sr) } -// ViewsAt returns the view at a specific height -func (p *Protocol) ViewsAt(ctx context.Context, sr protocol.StateReader) (protocol.View, error) { +// ViewAt returns the view at a specific height +func (p *Protocol) ViewAt(ctx context.Context, sr protocol.StateReader) (protocol.View, error) { featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx) height, err := sr.Height() if err != nil { @@ -708,10 +708,6 @@ func (p *Protocol) isActiveCandidate(ctx context.Context, csr CandidiateStateCom // ActiveCandidates returns all active candidates in candidate center func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) { - srHeight, err := sr.Height() - if err != nil { - return nil, errors.Wrap(err, "failed to get StateReader height") - } c, err := ConstructBaseView(sr) if err != nil { return nil, errors.Wrap(err, "failed to get ActiveCandidates") @@ -721,20 +717,23 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader for i := range list { if protocol.MustGetFeatureCtx(ctx).StoreVoteOfNFTBucketIntoView { var csVotes *big.Int - if protocol.MustGetFeatureCtx(ctx).CreatePostActionStates { - csVotes, err = p.contractStakingVotesFromView(ctx, list[i].GetIdentifier(), c.BaseView()) - if err != nil { - return nil, err - } - } else { - // specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind. - // the reason of using srHeight-1 is contract indexer is not updated before the block is committed. - csVotes, err = p.contractStakingVotesFromIndexer(ctx, list[i].GetIdentifier(), srHeight-1) - if err != nil { - return nil, err - } + // if protocol.MustGetFeatureCtx(ctx).CreatePostActionStates { + // csVotes, err = p.contractStakingVotesFromView(ctx, list[i].GetIdentifier(), c.BaseView()) + // if err != nil { + // return nil, err + // } + // } else { + // // specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind. + // // the reason of using srHeight-1 is contract indexer is not updated before the block is committed. + // csVotes, err = p.contractStakingVotesFromIndexer(ctx, list[i].GetIdentifier(), srHeight-1) + // if err != nil { + // return nil, err + // } + // } + csVotes, err = p.contractStakingVotesFromVoteView(ctx, list[i].GetIdentifier(), c.BaseView()) + if err != nil { + return nil, err } - list[i].Votes.Add(list[i].Votes, csVotes) } active, err := p.isActiveCandidate(ctx, c, list[i]) @@ -989,7 +988,7 @@ func (p *Protocol) contractStakingVotesFromIndexer(ctx context.Context, candidat return votes, nil } -func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *viewData) (*big.Int, error) { +func (p *Protocol) contractStakingVotesFromVoteView(ctx context.Context, candidate address.Address, view *viewData) (*big.Int, error) { featureCtx := protocol.MustGetFeatureCtx(ctx) votes := big.NewInt(0) views := []ContractStakeView{} @@ -1003,17 +1002,40 @@ func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate a views = append(views, view.contractsStake.v3) } for _, cv := range views { - btks, err := cv.BucketsByCandidate(candidate) - if err != nil { - return nil, errors.Wrap(err, "failed to get BucketsByCandidate from contractStakingIndexer") - } - for _, b := range btks { - votes.Add(votes, p.contractBucketVotes(featureCtx, b)) + v := cv.CandidateStakeVotes(ctx, candidate) + if v == nil { + continue } + votes.Add(votes, v) } return votes, nil } +// func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *viewData) (*big.Int, error) { +// featureCtx := protocol.MustGetFeatureCtx(ctx) +// votes := big.NewInt(0) +// views := []ContractStakeView{} +// if p.contractStakingIndexer != nil && featureCtx.AddContractStakingVotes { +// views = append(views, view.contractsStake.v1) +// } +// if p.contractStakingIndexerV2 != nil && !featureCtx.LimitedStakingContract { +// views = append(views, view.contractsStake.v2) +// } +// if p.contractStakingIndexerV3 != nil && featureCtx.TimestampedStakingContract { +// views = append(views, view.contractsStake.v3) +// } +// for _, cv := range views { +// btks, err := cv.BucketsByCandidate(candidate) +// if err != nil { +// return nil, errors.Wrap(err, "failed to get BucketsByCandidate from contractStakingIndexer") +// } +// for _, b := range btks { +// votes.Add(votes, p.contractBucketVotes(featureCtx, b)) +// } +// } +// return votes, nil +// } + func (p *Protocol) contractBucketVotes(fCtx protocol.FeatureCtx, bkt *VoteBucket) *big.Int { votes := big.NewInt(0) if bkt.isUnstaked() { diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go index 12a6252d7b..f6be1591e2 100644 --- a/action/protocol/staking/stakeview_builder.go +++ b/action/protocol/staking/stakeview_builder.go @@ -47,6 +47,7 @@ func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateR if b.blockdao == nil { return nil, errors.Errorf("blockdao is nil, cannot build view for height %d", height) } + handler := b.indexer.CreateMemoryEventHandler(ctx) for h := viewHeight + 1; h <= height; h++ { receipts, err := b.blockdao.GetReceipts(h) if err != nil { @@ -60,7 +61,7 @@ func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateR BlockHeight: h, BlockTimeStamp: header.Timestamp(), }) - if err = view.AddBlockReceipts(ctx, receipts); err != nil { + if err = view.AddBlockReceipts(ctx, receipts, handler); err != nil { return nil, errors.Wrapf(err, "failed to build view with block at height %d", h) } } diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index ca21ef6b51..ec80d783a7 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -33,9 +33,10 @@ type ( Handle(ctx context.Context, receipt *action.Receipt) error // Migrate writes the bucket types and buckets to the state manager Migrate(EventHandler) error - // BucketsByCandidate returns the buckets by candidate address - BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) - AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error + // CandidateStakeVotes returns the candidate votes by identity address + CandidateStakeVotes(ctx context.Context, id address.Address) *big.Int + // AddBlockReceipts adds block receipts to the contract stake view + AddBlockReceipts(ctx context.Context, receipts []*action.Receipt, handler EventHandler) error // Height returns the height of the contract stake view Height() uint64 } diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 00be8ec797..4779077095 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -92,6 +92,10 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even ) } +func (s *Indexer) CreateMemoryEventHandler(ctx context.Context) staking.EventHandler { + return newContractStakingDirty(newWrappedCache(s.cache)) +} + // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { cssr := contractstaking.NewStateReader(sr) diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index eaaa68f275..df69b2aec2 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -2,6 +2,7 @@ package contractstaking import ( "context" + "math/big" "slices" "github.com/iotexproject/iotex-address/address" @@ -108,6 +109,10 @@ func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, er return s.assembleBuckets(ids, types, infos), nil } +func (s *stakeView) CandidateStakeVotes(ctx context.Context, candidate address.Address) *big.Int { + return nil +} + func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { return assembleBucket(token, bi, bt, s.contractAddr.String(), s.genBlockDurationFn(s.height)) } @@ -140,7 +145,7 @@ func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error return nil } -func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { +func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt, _ staking.EventHandler) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight expectHeight := s.height + 1 diff --git a/blockindex/contractstaking/voteview.go b/blockindex/contractstaking/voteview.go new file mode 100644 index 0000000000..8040395baa --- /dev/null +++ b/blockindex/contractstaking/voteview.go @@ -0,0 +1 @@ +package contractstaking diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go new file mode 100644 index 0000000000..c6a8240bd5 --- /dev/null +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -0,0 +1,204 @@ +package stakingindex + +import ( + "math/big" + + "github.com/gogo/protobuf/proto" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex/stakingpb" +) + +type CandidateVotes interface { + Clone() CandidateVotes + Votes(fCtx protocol.FeatureCtx, cand string) *big.Int + Add(cand string, amount *big.Int, votes *big.Int) + Commit() + Base() CandidateVotes + IsDirty() bool + Serialize() ([]byte, error) + Deserialize(data []byte) error +} + +type candidate struct { + // total stake amount of candidate + amount *big.Int + // total weighted votes of candidate + votes *big.Int +} + +type candidateVotes struct { + cands map[string]*candidate +} + +type candidateVotesWraper struct { + base CandidateVotes + change *candidateVotes +} + +type candidateVotesWraperCommitInClone struct { + *candidateVotesWraper +} + +func newCandidate() *candidate { + return &candidate{ + amount: big.NewInt(0), + votes: big.NewInt(0), + } +} + +func (cv *candidateVotes) Clone() CandidateVotes { + newCands := make(map[string]*candidate) + for cand, c := range cv.cands { + newCands[cand] = &candidate{ + amount: new(big.Int).Set(c.amount), + votes: new(big.Int).Set(c.votes), + } + } + return &candidateVotes{ + cands: newCands, + } +} + +func (cv *candidateVotes) IsDirty() bool { + return false +} + +func (cv *candidateVotes) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int { + if !fCtx.FixContractStakingWeightedVotes { + return cv.cands[cand].amount + } + return cv.cands[cand].votes +} + +func (cv *candidateVotes) Add(cand string, amount *big.Int, votes *big.Int) { + if cv.cands[cand] == nil { + cv.cands[cand] = newCandidate() + } + if amount != nil { + cv.cands[cand].amount = new(big.Int).Add(cv.cands[cand].amount, amount) + } + if votes != nil { + cv.cands[cand].votes = new(big.Int).Add(cv.cands[cand].votes, votes) + } +} + +func (cv *candidateVotes) Serialize() ([]byte, error) { + cl := stakingpb.CandidateList{} + for cand, c := range cv.cands { + cl.Candidates = append(cl.Candidates, &stakingpb.Candidate{ + Address: cand, + Votes: c.votes.String(), + Amount: c.amount.String(), + }) + } + return proto.Marshal(&cl) +} + +func (cv *candidateVotes) Deserialize(data []byte) error { + cl := stakingpb.CandidateList{} + if err := proto.Unmarshal(data, &cl); err != nil { + return errors.Wrap(err, "failed to unmarshal candidate list") + } + for _, c := range cl.Candidates { + votes, ok := new(big.Int).SetString(c.Votes, 10) + if !ok { + return errors.Errorf("failed to parse votes: %s", c.Votes) + } + amount, ok := new(big.Int).SetString(c.Amount, 10) + if !ok { + return errors.Errorf("failed to parse amount: %s", c.Amount) + } + cv.Add(c.Address, amount, votes) + } + return nil +} + +func (cv *candidateVotes) Commit() { + // nothing to do +} + +func (cv *candidateVotes) Base() CandidateVotes { + return cv +} + +func newCandidateVotes() *candidateVotes { + return &candidateVotes{ + cands: make(map[string]*candidate), + } +} + +func newCandidateVotesWrapper(base CandidateVotes) *candidateVotesWraper { + return &candidateVotesWraper{ + base: base, + change: newCandidateVotes(), + } +} + +func (cv *candidateVotesWraper) Clone() CandidateVotes { + return &candidateVotesWraper{ + base: cv.base.Clone(), + change: cv.change.Clone().(*candidateVotes), + } +} + +func (cv *candidateVotesWraper) IsDirty() bool { + return cv.change.IsDirty() || cv.base.IsDirty() +} + +func (cv *candidateVotesWraper) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int { + base := cv.base.Votes(fCtx, cand) + change := cv.change.Votes(fCtx, cand) + if change == nil { + return base + } + if base == nil { + return nil + } + return new(big.Int).Add(base, change) +} + +func (cv *candidateVotesWraper) Add(cand string, amount *big.Int, votes *big.Int) { + cv.change.Add(cand, amount, votes) +} + +func (cv *candidateVotesWraper) Commit() { + // Commit the changes to the base + for cand, change := range cv.change.cands { + cv.base.Add(cand, change.amount, change.votes) + } + cv.change = newCandidateVotes() + // base commit + cv.base.Commit() +} + +func (cv *candidateVotesWraper) Serialize() ([]byte, error) { + return nil, errors.New("not implemented") +} + +func (cv *candidateVotesWraper) Deserialize(data []byte) error { + return errors.New("not implemented") +} + +func (cv *candidateVotesWraper) Base() CandidateVotes { + return cv.base +} + +func newCandidateVotesWrapperCommitInClone(base CandidateVotes) *candidateVotesWraperCommitInClone { + return &candidateVotesWraperCommitInClone{ + candidateVotesWraper: newCandidateVotesWrapper(base), + } +} + +func (cv *candidateVotesWraperCommitInClone) Clone() CandidateVotes { + return &candidateVotesWraperCommitInClone{ + candidateVotesWraper: cv.candidateVotesWraper.Clone().(*candidateVotesWraper), + } +} + +func (cv *candidateVotesWraperCommitInClone) Commit() { + cv.base = cv.base.Clone() + cv.candidateVotesWraper.Commit() + return +} diff --git a/systemcontractindex/stakingindex/event_handler.go b/systemcontractindex/stakingindex/event_handler.go index abad132189..b9dd0a3cfa 100644 --- a/systemcontractindex/stakingindex/event_handler.go +++ b/systemcontractindex/stakingindex/event_handler.go @@ -4,6 +4,7 @@ import ( "github.com/pkg/errors" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 96fbaa6911..882379baa2 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -14,6 +14,7 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/systemcontractindex" @@ -44,17 +45,19 @@ type ( PutBlock(ctx context.Context, blk *block.Block) error LoadStakeView(context.Context, protocol.StateReader) (staking.ContractStakeView, error) CreateEventProcessor(context.Context, staking.EventHandler) staking.EventProcessor + CreateMemoryEventHandler(context.Context) staking.EventHandler } // Indexer is the staking indexer Indexer struct { - common *systemcontractindex.IndexerCommon - cache *base // in-memory cache, used to query index data - mutex sync.RWMutex - blocksToDuration blocksDurationAtFn // function to calculate duration from block range - bucketNS string - ns string - muteHeight uint64 - timestamped bool + common *systemcontractindex.IndexerCommon + cache *base // in-memory cache, used to query index data + mutex sync.RWMutex + blocksToDuration blocksDurationAtFn // function to calculate duration from block range + bucketNS string + ns string + muteHeight uint64 + timestamped bool + calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn } // IndexerOption is the option to create an indexer IndexerOption func(*Indexer) @@ -130,6 +133,11 @@ func (s *Indexer) CreateEventProcessor(ctx context.Context, handler staking.Even ) } +// CreateMemoryEventHandler creates a new memory event handler +func (s *Indexer) CreateMemoryEventHandler(ctx context.Context) staking.EventHandler { + return newEventHandler(s.bucketNS, newWrappedCache(s.cache)) +} + // LoadStakeView loads the contract stake view from state reader func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { s.mutex.RLock() @@ -148,43 +156,45 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s return nil, err } } - return &stakeView{ - cache: s.cache.Clone(), - height: s.common.Height(), - contractAddr: s.common.ContractAddress(), - muteHeight: s.muteHeight, - timestamped: s.timestamped, - startHeight: s.common.StartHeight(), - bucketNS: s.bucketNS, - genBlockDurationFn: s.genBlockDurationFn, + cur := s.createCandidateVotes(s.cache.buckets) + flusher, err := db.NewKVStoreFlusher(s.common.KVStore(), batch.NewCachedBatch()) + if err != nil { + return nil, errors.Wrapf(err, "failed to create KVStoreFlusher") + } + handler, err := newVoteViewEventHandler(cur, s.calculateUnmutedVoteWeight, s.common.ContractAddress().String(), flusher.KVStoreWithBuffer()) + if err != nil { + return nil, err + } + return &voteView{ + indexer: s, + height: s.common.Height(), + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + timestamped: s.timestamped, + startHeight: s.common.StartHeight(), + bucketNS: s.bucketNS, + cur: s.createCandidateVotes(s.cache.buckets), + handler: handler, + calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, }, nil } // otherwise, we need to read from state reader - ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(contractAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to get buckets for contract %s", contractAddr) - } - if len(ids) != len(buckets) { - return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) + vv := &voteView{ + indexer: s, + height: csrHeight, + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + startHeight: s.common.StartHeight(), + timestamped: s.timestamped, + bucketNS: s.bucketNS, + cur: newCandidateVotes(), + handler: nil, + calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, } - cache := &base{} - for i, b := range buckets { - if b == nil { - return nil, errors.New("bucket is nil") - } - b.IsTimestampBased = s.timestamped - cache.PutBucket(ids[i], b) + if err = vv.loadVotes(ctx, sr); err != nil { + return nil, err } - return &stakeView{ - cache: cache, - height: csrHeight, - contractAddr: s.common.ContractAddress(), - muteHeight: s.muteHeight, - startHeight: s.common.StartHeight(), - timestamped: s.timestamped, - bucketNS: s.bucketNS, - genBlockDurationFn: s.genBlockDurationFn, - }, nil + return vv, nil } // Height returns the tip block height @@ -363,3 +373,14 @@ func (s *Indexer) genBlockDurationFn(view uint64) blocksDurationFn { return s.blocksToDuration(start, end, view) } } + +func (s *Indexer) createCandidateVotes(bkts map[uint64]*Bucket) CandidateVotes { + res := newCandidateVotes() + for _, bkt := range bkts { + if bkt.UnstakedAt != maxStakingNumber || bkt.Muted { + continue + } + res.Add(bkt.Candidate.String(), bkt.StakedAmount, s.calculateUnmutedVoteWeight(bkt)) + } + return res +} diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go deleted file mode 100644 index fac3a7c55c..0000000000 --- a/systemcontractindex/stakingindex/stakeview.go +++ /dev/null @@ -1,130 +0,0 @@ -package stakingindex - -import ( - "context" - "slices" - - "github.com/iotexproject/iotex-address/address" - "github.com/pkg/errors" - - "github.com/iotexproject/iotex-core/v2/action" - "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking" -) - -type stakeView struct { - cache indexerCache - height uint64 - startHeight uint64 - contractAddr address.Address - muteHeight uint64 - timestamped bool - bucketNS string - genBlockDurationFn func(view uint64) blocksDurationFn -} - -func (s *stakeView) Height() uint64 { - return s.height -} - -func (s *stakeView) Wrap() staking.ContractStakeView { - return &stakeView{ - cache: newWrappedCache(s.cache), - height: s.height, - startHeight: s.startHeight, - contractAddr: s.contractAddr, - muteHeight: s.muteHeight, - timestamped: s.timestamped, - bucketNS: s.bucketNS, - genBlockDurationFn: s.genBlockDurationFn, - } -} - -func (s *stakeView) Fork() staking.ContractStakeView { - return &stakeView{ - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, - startHeight: s.startHeight, - contractAddr: s.contractAddr, - muteHeight: s.muteHeight, - timestamped: s.timestamped, - bucketNS: s.bucketNS, - genBlockDurationFn: s.genBlockDurationFn, - } -} - -func (s *stakeView) IsDirty() bool { - return s.cache.IsDirty() -} - -func (s *stakeView) Migrate(handler staking.EventHandler) error { - ids := s.cache.BucketIdxs() - slices.Sort(ids) - buckets := s.cache.Buckets(ids) - for _, id := range ids { - if err := handler.PutBucket(s.contractAddr, id, buckets[id]); err != nil { - return err - } - } - return nil -} - -func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket, error) { - idxs := s.cache.BucketIdsByCandidate(candidate) - bkts := s.cache.Buckets(idxs) - // filter out muted buckets - idxsFiltered := make([]uint64, 0, len(bkts)) - bktsFiltered := make([]*Bucket, 0, len(bkts)) - for i := range bkts { - if !bkts[i].Muted { - idxsFiltered = append(idxsFiltered, idxs[i]) - bktsFiltered = append(bktsFiltered, bkts[i]) - } - } - vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.contractAddr.String(), s.genBlockDurationFn(s.height)) - return vbs, nil -} - -func (s *stakeView) CreatePreStates(ctx context.Context) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - s.height = blkCtx.BlockHeight - return nil -} - -func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight - return newEventProcessor( - s.contractAddr, blkCtx, newEventHandler(s.bucketNS, s.cache), s.timestamped, muted, - ).ProcessReceipts(ctx, receipt) -} - -func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - height := blkCtx.BlockHeight - if height < s.startHeight { - return nil - } - if height != s.height+1 && height != s.startHeight { - return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) - } - ctx = protocol.WithBlockCtx(ctx, blkCtx) - muted := s.muteHeight > 0 && height >= s.muteHeight - if err := newEventProcessor( - s.contractAddr, blkCtx, newEventHandler(s.bucketNS, s.cache), s.timestamped, muted, - ).ProcessReceipts(ctx, receipts...); err != nil { - return errors.Wrapf(err, "failed to handle receipts at height %d", height) - } - s.height = height - return nil -} - -func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { - cache, err := s.cache.Commit(ctx, s.contractAddr, s.timestamped, sm) - if err != nil { - return err - } - s.cache = cache - - return nil -} diff --git a/systemcontractindex/stakingindex/stakingpb/staking.pb.go b/systemcontractindex/stakingindex/stakingpb/staking.pb.go index 84271a9666..f5617bfe43 100644 --- a/systemcontractindex/stakingindex/stakingpb/staking.pb.go +++ b/systemcontractindex/stakingindex/stakingpb/staking.pb.go @@ -139,6 +139,116 @@ func (x *Bucket) GetTimestamped() bool { return false } +type Candidate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Votes string `protobuf:"bytes,2,opt,name=votes,proto3" json:"votes,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (x *Candidate) Reset() { + *x = Candidate{} + if protoimpl.UnsafeEnabled { + mi := &file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Candidate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Candidate) ProtoMessage() {} + +func (x *Candidate) ProtoReflect() protoreflect.Message { + mi := &file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Candidate.ProtoReflect.Descriptor instead. +func (*Candidate) Descriptor() ([]byte, []int) { + return file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDescGZIP(), []int{1} +} + +func (x *Candidate) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *Candidate) GetVotes() string { + if x != nil { + return x.Votes + } + return "" +} + +func (x *Candidate) GetAmount() string { + if x != nil { + return x.Amount + } + return "" +} + +type CandidateList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Candidates []*Candidate `protobuf:"bytes,1,rep,name=candidates,proto3" json:"candidates,omitempty"` +} + +func (x *CandidateList) Reset() { + *x = CandidateList{} + if protoimpl.UnsafeEnabled { + mi := &file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CandidateList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CandidateList) ProtoMessage() {} + +func (x *CandidateList) ProtoReflect() protoreflect.Message { + mi := &file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CandidateList.ProtoReflect.Descriptor instead. +func (*CandidateList) Descriptor() ([]byte, []int) { + return file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDescGZIP(), []int{2} +} + +func (x *CandidateList) GetCandidates() []*Candidate { + if x != nil { + return x.Candidates + } + return nil +} + var File_systemcontractindex_stakingindex_stakingpb_staking_proto protoreflect.FileDescriptor var file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDesc = []byte{ @@ -163,12 +273,23 @@ var file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDesc = []by 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x74, - 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x22, 0x53, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, + 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4d, 0x0a, 0x0d, 0x43, + 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x0a, + 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x74, 0x61, 0x6b, 0x69, + 0x6e, 0x67, 0x70, 0x62, 0x2e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, + 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -183,16 +304,19 @@ func file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDescGZIP() return file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDescData } -var file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_systemcontractindex_stakingindex_stakingpb_staking_proto_goTypes = []interface{}{ - (*Bucket)(nil), // 0: contractstakingpb.Bucket + (*Bucket)(nil), // 0: contractstakingpb.Bucket + (*Candidate)(nil), // 1: contractstakingpb.Candidate + (*CandidateList)(nil), // 2: contractstakingpb.CandidateList } var file_systemcontractindex_stakingindex_stakingpb_staking_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: contractstakingpb.CandidateList.candidates:type_name -> contractstakingpb.Candidate + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_systemcontractindex_stakingindex_stakingpb_staking_proto_init() } @@ -213,6 +337,30 @@ func file_systemcontractindex_stakingindex_stakingpb_staking_proto_init() { return nil } } + file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Candidate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_systemcontractindex_stakingindex_stakingpb_staking_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CandidateList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -220,7 +368,7 @@ func file_systemcontractindex_stakingindex_stakingpb_staking_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_systemcontractindex_stakingindex_stakingpb_staking_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/systemcontractindex/stakingindex/stakingpb/staking.proto b/systemcontractindex/stakingindex/stakingpb/staking.proto index f9263e724b..181933ee5c 100644 --- a/systemcontractindex/stakingindex/stakingpb/staking.proto +++ b/systemcontractindex/stakingindex/stakingpb/staking.proto @@ -19,4 +19,14 @@ message Bucket { uint64 unstakedAt = 7; bool muted = 8; bool timestamped = 9; +} + +message Candidate { + string address = 1; + string votes = 2; + string amount = 3; +} + +message CandidateList { + repeated Candidate candidates = 1; } \ No newline at end of file diff --git a/systemcontractindex/stakingindex/vote_view_handler.go b/systemcontractindex/stakingindex/vote_view_handler.go new file mode 100644 index 0000000000..ba1cf38567 --- /dev/null +++ b/systemcontractindex/stakingindex/vote_view_handler.go @@ -0,0 +1,128 @@ +package stakingindex + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" +) + +type calculateUnmutedVoteWeightFn func(*contractstaking.Bucket) *big.Int + +type voteViewEventHandler struct { + staking.EventHandler + view CandidateVotes + + calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn +} + +func newVoteViewEventHandler(view CandidateVotes, fn calculateUnmutedVoteWeightFn, bucketNS string, store db.KVStore) (*voteViewEventHandler, error) { + storeWithBuffer, err := newStoreWithBuffer(bucketNS, store) + if err != nil { + return nil, err + } + return &voteViewEventHandler{ + EventHandler: storeWithBuffer, + view: view, + calculateUnmutedVoteWeight: fn, + }, nil +} + +func newVoteViewEventHandlerWrapper(handler staking.EventHandler, view CandidateVotes, fn calculateUnmutedVoteWeightFn) *voteViewEventHandler { + return &voteViewEventHandler{ + EventHandler: handler, + view: view, + calculateUnmutedVoteWeight: fn, + } +} + +func (s *voteViewEventHandler) PutBucket(addr address.Address, id uint64, bucket *contractstaking.Bucket) error { + org, err := s.EventHandler.DeductBucket(addr, id) + switch errors.Cause(err) { + case nil, contractstaking.ErrBucketNotExist: + default: + return errors.Wrapf(err, "failed to deduct bucket") + } + + deltaVotes, deltaAmount := s.calculateBucket(bucket) + if org != nil { + orgVotes, orgAmount := s.calculateBucket(org) + deltaVotes = new(big.Int).Sub(deltaVotes, orgVotes) + deltaAmount = new(big.Int).Sub(deltaAmount, orgAmount) + } + s.view.Add(addr.String(), deltaAmount, deltaVotes) + + s.EventHandler.PutBucket(addr, id, bucket) + return nil +} + +func (s *voteViewEventHandler) DeleteBucket(addr address.Address, id uint64) error { + org, err := s.EventHandler.DeductBucket(addr, id) + switch errors.Cause(err) { + case nil: + // subtract original votes + deltaVotes, deltaAmount := s.calculateBucket(org) + s.view.Add(addr.String(), deltaAmount.Neg(deltaAmount), deltaVotes.Neg(deltaVotes)) + case contractstaking.ErrBucketNotExist: + // do nothing + default: + return errors.Wrapf(err, "failed to deduct bucket") + } + return s.EventHandler.DeleteBucket(addr, id) +} + +func (s *voteViewEventHandler) calculateBucket(bucket *contractstaking.Bucket) (votes *big.Int, amount *big.Int) { + if bucket.Muted { + return big.NewInt(0), big.NewInt(0) + } + return s.calculateUnmutedVoteWeight(bucket), bucket.StakedAmount +} + +type storeWithBuffer struct { + ns string + store db.KVStore +} + +func newStoreWithBuffer(ns string, store db.KVStore) (*storeWithBuffer, error) { + return &storeWithBuffer{ + ns: ns, + store: store, + }, nil +} + +func (swb *storeWithBuffer) PutBucketType(addr address.Address, bt *contractstaking.BucketType) error { + return errors.New("not supported") +} + +func (swb *storeWithBuffer) DeductBucket(addr address.Address, id uint64) (*contractstaking.Bucket, error) { + data, err := swb.store.Get(swb.ns, byteutil.Uint64ToBytesBigEndian(id)) + switch errors.Cause(err) { + case nil: + case db.ErrBucketNotExist, db.ErrNotExist: + return nil, contractstaking.ErrBucketNotExist + default: + return nil, errors.Wrap(err, "failed to get bucket") + } + bucket := new(contractstaking.Bucket) + if err := bucket.Deserialize(data); err != nil { + return nil, errors.Wrap(err, "failed to deserialize bucket") + } + return bucket, nil +} + +func (swb *storeWithBuffer) PutBucket(addr address.Address, id uint64, bkt *contractstaking.Bucket) error { + data, err := bkt.Serialize() + if err != nil { + return errors.Wrap(err, "failed to serialize bucket") + } + return swb.store.Put(swb.ns, byteutil.Uint64ToBytesBigEndian(id), data) +} + +func (swb *storeWithBuffer) DeleteBucket(addr address.Address, id uint64) error { + return swb.store.Delete(swb.ns, byteutil.Uint64ToBytesBigEndian(id)) +} diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go new file mode 100644 index 0000000000..9899bea3b5 --- /dev/null +++ b/systemcontractindex/stakingindex/voteview.go @@ -0,0 +1,179 @@ +package stakingindex + +import ( + "context" + "math/big" + "slices" + + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" +) + +type voteView struct { + // read only fields + muteHeight, startHeight uint64 + contractAddr address.Address + timestamped bool + ns, bucketNS string + calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn + indexer *Indexer + + // height of the view + height uint64 + // current candidate votes + cur CandidateVotes + + handler staking.EventHandler +} + +func (s *voteView) Height() uint64 { + return s.height +} + +func (s *voteView) Wrap() staking.ContractStakeView { + cur := newCandidateVotesWrapper(s.cur) + var handler staking.EventHandler + if s.handler != nil { + handler = newVoteViewEventHandlerWrapper(s.handler, cur, s.calculateUnmutedVoteWeight) + } + return &voteView{ + muteHeight: s.muteHeight, + contractAddr: s.contractAddr, + timestamped: s.timestamped, + height: s.height, + cur: cur, + handler: handler, + calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, + indexer: s.indexer, + } +} + +func (s *voteView) Fork() staking.ContractStakeView { + cur := newCandidateVotesWrapperCommitInClone(s.cur) + var handler staking.EventHandler + if s.handler != nil { + handler = newVoteViewEventHandlerWrapper(s.handler, cur, s.calculateUnmutedVoteWeight) + } + return &voteView{ + muteHeight: s.muteHeight, + contractAddr: s.contractAddr, + timestamped: s.timestamped, + height: s.height, + cur: cur, + handler: handler, + calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, + indexer: s.indexer, + } +} + +func (s *voteView) IsDirty() bool { + return s.cur.IsDirty() +} + +func (s *voteView) Migrate(handler staking.EventHandler) error { + var cache *base + if s.indexer != nil { + indexerHeight, err := s.indexer.Height() + if err != nil { + return err + } + if indexerHeight == s.height-1 { + cache = s.indexer.cache + } + } + if cache == nil { + return errors.Errorf("cannot migrate vote view at height %d without indexer cache at height %d", s.height, s.height-1) + } + ids := cache.BucketIdxs() + slices.Sort(ids) + buckets := cache.Buckets(ids) + for _, id := range ids { + if err := handler.PutBucket(s.contractAddr, id, buckets[id]); err != nil { + return err + } + } + return nil +} + +func (s *voteView) CandidateStakeVotes(ctx context.Context, candidate address.Address) *big.Int { + featureCtx := protocol.MustGetFeatureCtx(ctx) + if !featureCtx.CreatePostActionStates { + return s.cur.Base().Votes(featureCtx, candidate.String()) + } + return s.cur.Votes(featureCtx, candidate.String()) +} + +func (s *voteView) CreatePreStates(ctx context.Context) error { + blkCtx := protocol.MustGetBlockCtx(ctx) + s.height = blkCtx.BlockHeight + return nil +} + +func (s *voteView) Handle(ctx context.Context, receipt *action.Receipt) error { + if s.handler == nil { + return nil + } + blkCtx := protocol.MustGetBlockCtx(ctx) + muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight + return newEventProcessor( + s.contractAddr, blkCtx, s.handler, s.timestamped, muted, + ).ProcessReceipts(ctx, receipt) +} + +func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt, handler staking.EventHandler) error { + blkCtx := protocol.MustGetBlockCtx(ctx) + height := blkCtx.BlockHeight + if height < s.startHeight { + return nil + } + if height != s.height+1 && height != s.startHeight { + return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) + } + ctx = protocol.WithBlockCtx(ctx, blkCtx) + muted := s.muteHeight > 0 && height >= s.muteHeight + if err := newEventProcessor( + s.contractAddr, blkCtx, handler, s.timestamped, muted, + ).ProcessReceipts(ctx, receipts...); err != nil { + return errors.Wrapf(err, "failed to handle receipts at height %d", height) + } + s.height = height + return nil +} + +var ( + voteViewKey = []byte("voteview") + voteViewNSPrefix = "voterview" +) + +func (s *voteView) Commit(ctx context.Context, sm protocol.StateManager) error { + s.cur.Commit() + + return s.storeVotes(ctx, sm) +} + +func (s *voteView) storeVotes(ctx context.Context, sm protocol.StateManager) error { + if _, err := sm.PutState(s.cur, + protocol.KeyOption(voteViewKey), + protocol.NamespaceOption(s.namespace()), + protocol.SecondaryOnlyOption(), + ); err != nil { + return errors.Wrap(err, "failed to put candidate votes state") + } + return nil +} + +func (s *voteView) loadVotes(ctx context.Context, sr protocol.StateReader) error { + _, err := sr.State(s.cur, protocol.KeyOption(voteViewKey), protocol.NamespaceOption(s.namespace())) + if err != nil { + return errors.Wrap(err, "failed to get candidate votes state") + } + return nil +} + +func (s *voteView) namespace() string { + return voteViewNSPrefix + s.contractAddr.String() +} From 23020e311997aa9f62b8dfa97a31711a3ab03d58 Mon Sep 17 00:00:00 2001 From: envestcc Date: Mon, 8 Sep 2025 16:27:19 +0800 Subject: [PATCH 22/34] draft: refactor --- .../stakingindex/candidate_votes_manager.go | 56 ++++++ .../stakingindex/event_handler_factory.go | 54 ++++++ .../stakingindex/eventprocessor_builder.go | 35 ++++ systemcontractindex/stakingindex/index.go | 78 ++++---- .../stakingindex/vote_view_handler.go | 174 +++++++++++++++-- systemcontractindex/stakingindex/voteview.go | 183 ++++++++---------- 6 files changed, 430 insertions(+), 150 deletions(-) create mode 100644 systemcontractindex/stakingindex/candidate_votes_manager.go create mode 100644 systemcontractindex/stakingindex/event_handler_factory.go create mode 100644 systemcontractindex/stakingindex/eventprocessor_builder.go diff --git a/systemcontractindex/stakingindex/candidate_votes_manager.go b/systemcontractindex/stakingindex/candidate_votes_manager.go new file mode 100644 index 0000000000..9f5f7420b2 --- /dev/null +++ b/systemcontractindex/stakingindex/candidate_votes_manager.go @@ -0,0 +1,56 @@ +package stakingindex + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol" +) + +var ( + voteViewKey = []byte("voteview") + voteViewNSPrefix = "voterview" +) + +type CandidateVotesManager interface { + Load(ctx context.Context, sr protocol.StateReader) (CandidateVotes, error) + Store(ctx context.Context, sm protocol.StateManager, candVotes CandidateVotes) error +} + +type candidateVotesManager struct { + contractAddr address.Address +} + +// NewCandidateVotesManager creates a new instance of CandidateVotesManager +func NewCandidateVotesManager(contractAddr address.Address) CandidateVotesManager { + return &candidateVotesManager{ + contractAddr: contractAddr, + } +} + +func (s *candidateVotesManager) Store(ctx context.Context, sm protocol.StateManager, candVotes CandidateVotes) error { + if _, err := sm.PutState(candVotes, + protocol.KeyOption(voteViewKey), + protocol.NamespaceOption(s.namespace()), + protocol.SecondaryOnlyOption(), + ); err != nil { + return errors.Wrap(err, "failed to put candidate votes state") + } + return nil +} + +func (s *candidateVotesManager) Load(ctx context.Context, sr protocol.StateReader) (CandidateVotes, error) { + cur := newCandidateVotes() + _, err := sr.State(cur, protocol.KeyOption(voteViewKey), protocol.NamespaceOption(s.namespace())) + if err != nil { + return nil, errors.Wrap(err, "failed to get candidate votes state") + } + return cur, nil +} + +func (s *candidateVotesManager) namespace() string { + return voteViewNSPrefix + s.contractAddr.String() +} diff --git a/systemcontractindex/stakingindex/event_handler_factory.go b/systemcontractindex/stakingindex/event_handler_factory.go new file mode 100644 index 0000000000..89cb40a0eb --- /dev/null +++ b/systemcontractindex/stakingindex/event_handler_factory.go @@ -0,0 +1,54 @@ +package stakingindex + +import ( + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/iotexproject/iotex-core/v2/db" +) + +type eventHandlerFactory struct { + bucketNS string + store db.KVStore + fn CalculateUnmutedVoteWeightFn +} + +func NewEventHandlerFactory(bucketNS string, store db.KVStore) EventHandlerFactory { + return &eventHandlerFactory{ + bucketNS: bucketNS, + store: store, + } +} + +func (f *eventHandlerFactory) NewEventHandler(view CandidateVotes) (BucketStore, error) { + return newVoteViewEventHandler(view, f.fn, f.bucketNS, f.store) +} + +func (f *eventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes) (BucketStore, error) { + if storer, ok := handler.(interface{ KVStore() db.KVStore }); ok { + return newVoteViewEventHandler(view, f.fn, f.bucketNS, storer.KVStore()) + } + return nil, errors.New("handler does not support KVStore()") +} + +type contractEventHandlerFactory struct { + csr *contractstaking.ContractStakingStateReader + fn CalculateUnmutedVoteWeightFn +} + +func NewContractEventHandlerFactory(csr *contractstaking.ContractStakingStateReader, fn CalculateUnmutedVoteWeightFn) EventHandlerFactory { + return &contractEventHandlerFactory{ + csr: csr, + fn: fn, + } +} + +func (f *contractEventHandlerFactory) NewEventHandler(view CandidateVotes) (BucketStore, error) { + store := NewStoreWithContract(f.csr) + return newVoteViewEventHandlerWraper(store, view, f.fn) +} + +func (f *contractEventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes) (BucketStore, error) { + store := NewStoreWrapper(handler) + return newVoteViewEventHandlerWraper(store, view, f.fn) +} diff --git a/systemcontractindex/stakingindex/eventprocessor_builder.go b/systemcontractindex/stakingindex/eventprocessor_builder.go new file mode 100644 index 0000000000..c5b59a7570 --- /dev/null +++ b/systemcontractindex/stakingindex/eventprocessor_builder.go @@ -0,0 +1,35 @@ +package stakingindex + +import ( + "context" + + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" +) + +// EventProcessorBuilder is the interface to build event processor +type EventProcessorBuilder interface { + Build(context.Context, staking.EventHandler) staking.EventProcessor +} + +type eventProcessorBuilder struct { + contractAddr address.Address + timestamped bool + muteHeight uint64 +} + +func newEventProcessorBuilder(contractAddr address.Address, timestamped bool, muteHeight uint64) *eventProcessorBuilder { + return &eventProcessorBuilder{ + contractAddr: contractAddr, + timestamped: timestamped, + muteHeight: muteHeight, + } +} + +func (b *eventProcessorBuilder) Build(ctx context.Context, handler staking.EventHandler) staking.EventProcessor { + blkCtx := protocol.MustGetBlockCtx(ctx) + muted := b.muteHeight > 0 && blkCtx.BlockHeight >= b.muteHeight + return newEventProcessor(b.contractAddr, blkCtx, handler, b.timestamped, muted) +} diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 882379baa2..c12c947324 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -14,7 +14,6 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" - "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/systemcontractindex" @@ -57,7 +56,7 @@ type ( ns string muteHeight uint64 timestamped bool - calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn + calculateUnmutedVoteWeight CalculateUnmutedVoteWeightFn } // IndexerOption is the option to create an indexer IndexerOption func(*Indexer) @@ -149,6 +148,12 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s if err != nil { return nil, errors.Wrapf(err, "failed to get height for contract %s", contractAddr) } + cfg := &VoteViewConfig{ + ContractAddr: s.common.ContractAddress(), + StartHeight: s.common.StartHeight(), + MuteHeight: s.muteHeight, + Timestamped: s.timestamped, + } // contract staking state have not been initialized in state reader, we need to read from index if csrHeight == 0 { if !s.common.Started() { @@ -156,47 +161,38 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s return nil, err } } - cur := s.createCandidateVotes(s.cache.buckets) - flusher, err := db.NewKVStoreFlusher(s.common.KVStore(), batch.NewCachedBatch()) - if err != nil { - return nil, errors.Wrapf(err, "failed to create KVStoreFlusher") - } - handler, err := newVoteViewEventHandler(cur, s.calculateUnmutedVoteWeight, s.common.ContractAddress().String(), flusher.KVStoreWithBuffer()) - if err != nil { - return nil, err - } - return &voteView{ - indexer: s, - height: s.common.Height(), - contractAddr: s.common.ContractAddress(), - muteHeight: s.muteHeight, - timestamped: s.timestamped, - startHeight: s.common.StartHeight(), - bucketNS: s.bucketNS, - cur: s.createCandidateVotes(s.cache.buckets), - handler: handler, - calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, - }, nil + builder := NewEventHandlerFactory(s.bucketNS, s.common.KVStore()) + processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) + mgr := NewCandidateVotesManager(s.ContractAddress()) + return NewVoteView(cfg, s.common.Height(), s.createCandidateVotes(s.cache.buckets), builder, processorBuilder, s, mgr), nil } // otherwise, we need to read from state reader - vv := &voteView{ - indexer: s, - height: csrHeight, - contractAddr: s.common.ContractAddress(), - muteHeight: s.muteHeight, - startHeight: s.common.StartHeight(), - timestamped: s.timestamped, - bucketNS: s.bucketNS, - cur: newCandidateVotes(), - handler: nil, - calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, - } - if err = vv.loadVotes(ctx, sr); err != nil { + cache := NewContractBucketCache(s.common.ContractAddress(), csr) + builder := NewContractEventHandlerFactory(csr, s.calculateUnmutedVoteWeight) + processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) + mgr := NewCandidateVotesManager(s.ContractAddress()) + cur, err := mgr.Load(ctx, sr) + if err != nil { return nil, err } + vv := NewVoteView(cfg, csrHeight, cur, builder, processorBuilder, cache, mgr) return vv, nil } +// ContractStakingBuckets returns all the contract staking buckets +func (s *Indexer) ContractStakingBuckets() (uint64, map[uint64]*Bucket, error) { + s.mutex.RLock() + defer s.mutex.RUnlock() + + idxs := s.cache.BucketIdxs() + bkts := s.cache.Buckets(idxs) + res := make(map[uint64]*Bucket) + for i, id := range idxs { + res[id] = bkts[i] + } + return s.common.Height(), res, nil +} + // Height returns the tip block height func (s *Indexer) Height() (uint64, error) { s.mutex.RLock() @@ -375,12 +371,14 @@ func (s *Indexer) genBlockDurationFn(view uint64) blocksDurationFn { } func (s *Indexer) createCandidateVotes(bkts map[uint64]*Bucket) CandidateVotes { + return AggregateCandidateVotes(bkts, s.calculateUnmutedVoteWeight) +} + +// AggregateCandidateVotes aggregates the votes for each candidate from the given buckets +func AggregateCandidateVotes(bkts map[uint64]*Bucket, calculateUnmutedVoteWeight CalculateUnmutedVoteWeightFn) CandidateVotes { res := newCandidateVotes() for _, bkt := range bkts { - if bkt.UnstakedAt != maxStakingNumber || bkt.Muted { - continue - } - res.Add(bkt.Candidate.String(), bkt.StakedAmount, s.calculateUnmutedVoteWeight(bkt)) + res.Add(bkt.Candidate.String(), bkt.StakedAmount, calculateUnmutedVoteWeight(bkt)) } return res } diff --git a/systemcontractindex/stakingindex/vote_view_handler.go b/systemcontractindex/stakingindex/vote_view_handler.go index ba1cf38567..efbac3355a 100644 --- a/systemcontractindex/stakingindex/vote_view_handler.go +++ b/systemcontractindex/stakingindex/vote_view_handler.go @@ -6,43 +6,46 @@ import ( "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) -type calculateUnmutedVoteWeightFn func(*contractstaking.Bucket) *big.Int +type CalculateUnmutedVoteWeightFn func(*contractstaking.Bucket) *big.Int type voteViewEventHandler struct { - staking.EventHandler + BucketStore view CandidateVotes - calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn + calculateUnmutedVoteWeight CalculateUnmutedVoteWeightFn } -func newVoteViewEventHandler(view CandidateVotes, fn calculateUnmutedVoteWeightFn, bucketNS string, store db.KVStore) (*voteViewEventHandler, error) { +func newVoteViewEventHandler(view CandidateVotes, fn CalculateUnmutedVoteWeightFn, bucketNS string, store db.KVStore) (*voteViewEventHandler, error) { storeWithBuffer, err := newStoreWithBuffer(bucketNS, store) if err != nil { return nil, err } return &voteViewEventHandler{ - EventHandler: storeWithBuffer, + BucketStore: storeWithBuffer, view: view, calculateUnmutedVoteWeight: fn, }, nil } -func newVoteViewEventHandlerWrapper(handler staking.EventHandler, view CandidateVotes, fn calculateUnmutedVoteWeightFn) *voteViewEventHandler { +func NewVoteViewEventHandlerWraper(store BucketStore, view CandidateVotes, fn CalculateUnmutedVoteWeightFn) (BucketStore, error) { + return newVoteViewEventHandlerWraper(store, view, fn) +} + +func newVoteViewEventHandlerWraper(store BucketStore, view CandidateVotes, fn CalculateUnmutedVoteWeightFn) (*voteViewEventHandler, error) { return &voteViewEventHandler{ - EventHandler: handler, + BucketStore: store, view: view, calculateUnmutedVoteWeight: fn, - } + }, nil } func (s *voteViewEventHandler) PutBucket(addr address.Address, id uint64, bucket *contractstaking.Bucket) error { - org, err := s.EventHandler.DeductBucket(addr, id) + org, err := s.BucketStore.DeductBucket(addr, id) switch errors.Cause(err) { case nil, contractstaking.ErrBucketNotExist: default: @@ -57,12 +60,12 @@ func (s *voteViewEventHandler) PutBucket(addr address.Address, id uint64, bucket } s.view.Add(addr.String(), deltaAmount, deltaVotes) - s.EventHandler.PutBucket(addr, id, bucket) + s.BucketStore.PutBucket(addr, id, bucket) return nil } func (s *voteViewEventHandler) DeleteBucket(addr address.Address, id uint64) error { - org, err := s.EventHandler.DeductBucket(addr, id) + org, err := s.BucketStore.DeductBucket(addr, id) switch errors.Cause(err) { case nil: // subtract original votes @@ -73,7 +76,7 @@ func (s *voteViewEventHandler) DeleteBucket(addr address.Address, id uint64) err default: return errors.Wrapf(err, "failed to deduct bucket") } - return s.EventHandler.DeleteBucket(addr, id) + return s.BucketStore.DeleteBucket(addr, id) } func (s *voteViewEventHandler) calculateBucket(bucket *contractstaking.Bucket) (votes *big.Int, amount *big.Int) { @@ -126,3 +129,148 @@ func (swb *storeWithBuffer) PutBucket(addr address.Address, id uint64, bkt *cont func (swb *storeWithBuffer) DeleteBucket(addr address.Address, id uint64) error { return swb.store.Delete(swb.ns, byteutil.Uint64ToBytesBigEndian(id)) } + +func (swb *storeWithBuffer) KVStore() db.KVStore { + return swb.store +} + +type storeWithContract struct { + csr *contractstaking.ContractStakingStateReader + dirty map[string]map[uint64]*Bucket +} + +func NewStoreWithContract(csr *contractstaking.ContractStakingStateReader) *storeWithContract { + return &storeWithContract{ + csr: csr, + dirty: make(map[string]map[uint64]*Bucket), + } +} + +func (swb *storeWithContract) PutBucketType(addr address.Address, bt *contractstaking.BucketType) error { + return nil +} + +func (swb *storeWithContract) DeductBucket(addr address.Address, id uint64) (*contractstaking.Bucket, error) { + dirty, ok := swb.dirtyBucket(addr, id) + if ok { + if dirty == nil { + return nil, errors.Wrap(contractstaking.ErrBucketNotExist, "bucket not exist") + } + return dirty, nil + } + bucket, err := swb.csr.Bucket(addr, id) + if err != nil { + return nil, errors.Wrap(err, "failed to get bucket") + } + return bucket, nil +} + +func (swb *storeWithContract) PutBucket(addr address.Address, id uint64, bkt *contractstaking.Bucket) error { + if _, ok := swb.dirty[addr.String()]; !ok { + swb.dirty[addr.String()] = make(map[uint64]*Bucket) + } + swb.dirty[addr.String()][id] = bkt + return nil +} + +func (swb *storeWithContract) DeleteBucket(addr address.Address, id uint64) error { + if _, ok := swb.dirty[addr.String()]; !ok { + swb.dirty[addr.String()] = make(map[uint64]*Bucket) + } + swb.dirty[addr.String()][id] = nil + return nil +} + +func (swb *storeWithContract) dirtyBucket(addr address.Address, id uint64) (*Bucket, bool) { + if buckets, ok := swb.dirty[addr.String()]; ok { + if bkt, ok := buckets[id]; ok { + return bkt, true + } + } + return nil, false +} + +type contractBucketCache struct { + contractAddr address.Address + csr *contractstaking.ContractStakingStateReader +} + +// NewContractBucketCache creates a new instance of BucketCache +func NewContractBucketCache(contractAddr address.Address, csr *contractstaking.ContractStakingStateReader) BucketCache { + return &contractBucketCache{ + contractAddr: contractAddr, + csr: csr, + } +} + +func (cbc *contractBucketCache) ContractStakingBuckets() (uint64, map[uint64]*Bucket, error) { + height, err := cbc.csr.Height(cbc.contractAddr) + if err != nil { + return 0, nil, errors.Wrap(err, "failed to get height") + } + buckets := make(map[uint64]*Bucket) + ids, bkts, err := cbc.csr.Buckets(cbc.contractAddr) + if err != nil { + return 0, nil, errors.Wrap(err, "failed to get buckets") + } + for i := range ids { + buckets[ids[i]] = bkts[i] + } + return height, buckets, nil +} + +type storeWrapper struct { + store BucketStore + dirty map[string]map[uint64]*Bucket +} + +func NewStoreWrapper(store BucketStore) *storeWrapper { + return &storeWrapper{ + store: store, + dirty: make(map[string]map[uint64]*Bucket), + } +} + +func (swb *storeWrapper) PutBucketType(addr address.Address, bt *contractstaking.BucketType) error { + return errors.New("not supported") +} + +func (swb *storeWrapper) DeductBucket(addr address.Address, id uint64) (*contractstaking.Bucket, error) { + dirty, ok := swb.dirtyBucket(addr, id) + if ok { + if dirty == nil { + return nil, errors.Wrap(contractstaking.ErrBucketNotExist, "bucket not exist") + } + return dirty, nil + } + bucket, err := swb.store.DeductBucket(addr, id) + if err != nil { + return nil, errors.Wrap(err, "failed to get bucket") + } + return bucket, nil +} + +func (swb *storeWrapper) PutBucket(addr address.Address, id uint64, bkt *contractstaking.Bucket) error { + if _, ok := swb.dirty[addr.String()]; !ok { + swb.dirty[addr.String()] = make(map[uint64]*Bucket) + } + swb.dirty[addr.String()][id] = bkt + return nil +} + +func (swb *storeWrapper) DeleteBucket(addr address.Address, id uint64) error { + if _, ok := swb.dirty[addr.String()]; !ok { + swb.dirty[addr.String()] = make(map[uint64]*Bucket) + } + swb.dirty[addr.String()][id] = nil + return nil +} + +func (swb *storeWrapper) dirtyBucket(addr address.Address, id uint64) (*Bucket, bool) { + if buckets, ok := swb.dirty[addr.String()]; ok { + if bkt, ok := buckets[id]; ok { + return bkt, true + } + } + return nil, false +} diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go index 9899bea3b5..359079c4ac 100644 --- a/systemcontractindex/stakingindex/voteview.go +++ b/systemcontractindex/stakingindex/voteview.go @@ -3,7 +3,6 @@ package stakingindex import ( "context" "math/big" - "slices" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" @@ -13,21 +12,59 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking" ) -type voteView struct { - // read only fields - muteHeight, startHeight uint64 - contractAddr address.Address - timestamped bool - ns, bucketNS string - calculateUnmutedVoteWeight calculateUnmutedVoteWeightFn - indexer *Indexer - - // height of the view - height uint64 - // current candidate votes - cur CandidateVotes - - handler staking.EventHandler +type ( + // BucketCache is the interface to get all buckets from the underlying storage + BucketCache interface { + ContractStakingBuckets() (uint64, map[uint64]*Bucket, error) + } + BucketStore staking.EventHandler + // VoteViewConfig is the configuration for the vote view + VoteViewConfig struct { + MuteHeight, StartHeight uint64 + ContractAddr address.Address + Timestamped bool + } + EventHandlerFactory interface { + NewEventHandler(CandidateVotes) (BucketStore, error) + NewEventHandlerWithStore(BucketStore, CandidateVotes) (BucketStore, error) + } + + voteView struct { + config *VoteViewConfig + + height uint64 + // current candidate votes + cur CandidateVotes + + // bucketCache is used to migrate buckets from the underlying storage + bucketCache BucketCache + // handler is used as staging bucket storage for handling events + handler BucketStore + + store CandidateVotesManager + + handlerBuilder EventHandlerFactory + processorBuilder EventProcessorBuilder + } +) + +func NewVoteView(cfg *VoteViewConfig, + height uint64, + cur CandidateVotes, + handlerBuilder EventHandlerFactory, + processorBuilder EventProcessorBuilder, + bucketCache BucketCache, + store CandidateVotesManager, +) staking.ContractStakeView { + return &voteView{ + config: cfg, + height: height, + cur: cur, + handlerBuilder: handlerBuilder, + processorBuilder: processorBuilder, + bucketCache: bucketCache, + store: store, + } } func (s *voteView) Height() uint64 { @@ -36,37 +73,31 @@ func (s *voteView) Height() uint64 { func (s *voteView) Wrap() staking.ContractStakeView { cur := newCandidateVotesWrapper(s.cur) - var handler staking.EventHandler - if s.handler != nil { - handler = newVoteViewEventHandlerWrapper(s.handler, cur, s.calculateUnmutedVoteWeight) + handler, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + if err != nil { + panic(errors.Wrap(err, "failed to wrap vote view event handler")) } return &voteView{ - muteHeight: s.muteHeight, - contractAddr: s.contractAddr, - timestamped: s.timestamped, - height: s.height, - cur: cur, - handler: handler, - calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, - indexer: s.indexer, + config: s.config, + height: s.height, + cur: cur, + handler: handler, + bucketCache: s.bucketCache, } } func (s *voteView) Fork() staking.ContractStakeView { cur := newCandidateVotesWrapperCommitInClone(s.cur) - var handler staking.EventHandler - if s.handler != nil { - handler = newVoteViewEventHandlerWrapper(s.handler, cur, s.calculateUnmutedVoteWeight) + handler, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + if err != nil { + panic(errors.Wrap(err, "failed to fork vote view event handler")) } return &voteView{ - muteHeight: s.muteHeight, - contractAddr: s.contractAddr, - timestamped: s.timestamped, - height: s.height, - cur: cur, - handler: handler, - calculateUnmutedVoteWeight: s.calculateUnmutedVoteWeight, - indexer: s.indexer, + config: s.config, + height: s.height, + cur: cur, + handler: handler, + bucketCache: s.bucketCache, } } @@ -75,24 +106,15 @@ func (s *voteView) IsDirty() bool { } func (s *voteView) Migrate(handler staking.EventHandler) error { - var cache *base - if s.indexer != nil { - indexerHeight, err := s.indexer.Height() - if err != nil { - return err - } - if indexerHeight == s.height-1 { - cache = s.indexer.cache - } + height, buckets, err := s.bucketCache.ContractStakingBuckets() + if err != nil { + return err } - if cache == nil { - return errors.Errorf("cannot migrate vote view at height %d without indexer cache at height %d", s.height, s.height-1) + if height != s.height-1 { + return errors.Errorf("bucket cache height %d does not match vote view height %d", height, s.height) } - ids := cache.BucketIdxs() - slices.Sort(ids) - buckets := cache.Buckets(ids) - for _, id := range ids { - if err := handler.PutBucket(s.contractAddr, id, buckets[id]); err != nil { + for id := range buckets { + if err := handler.PutBucket(s.config.ContractAddr, id, buckets[id]); err != nil { return err } } @@ -110,70 +132,37 @@ func (s *voteView) CandidateStakeVotes(ctx context.Context, candidate address.Ad func (s *voteView) CreatePreStates(ctx context.Context) error { blkCtx := protocol.MustGetBlockCtx(ctx) s.height = blkCtx.BlockHeight + handler, err := s.handlerBuilder.NewEventHandler(s.cur) + if err != nil { + return err + } + s.handler = handler return nil } func (s *voteView) Handle(ctx context.Context, receipt *action.Receipt) error { - if s.handler == nil { - return nil - } - blkCtx := protocol.MustGetBlockCtx(ctx) - muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight - return newEventProcessor( - s.contractAddr, blkCtx, s.handler, s.timestamped, muted, - ).ProcessReceipts(ctx, receipt) + return s.processorBuilder.Build(ctx, s.handler).ProcessReceipts(ctx, receipt) } func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt, handler staking.EventHandler) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight - if height < s.startHeight { + if height < s.config.StartHeight { return nil } - if height != s.height+1 && height != s.startHeight { + if height != s.height+1 && height != s.config.StartHeight { return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) } ctx = protocol.WithBlockCtx(ctx, blkCtx) - muted := s.muteHeight > 0 && height >= s.muteHeight - if err := newEventProcessor( - s.contractAddr, blkCtx, handler, s.timestamped, muted, - ).ProcessReceipts(ctx, receipts...); err != nil { + if err := s.processorBuilder.Build(ctx, s.handler).ProcessReceipts(ctx, receipts...); err != nil { return errors.Wrapf(err, "failed to handle receipts at height %d", height) } s.height = height return nil } -var ( - voteViewKey = []byte("voteview") - voteViewNSPrefix = "voterview" -) - func (s *voteView) Commit(ctx context.Context, sm protocol.StateManager) error { s.cur.Commit() - return s.storeVotes(ctx, sm) -} - -func (s *voteView) storeVotes(ctx context.Context, sm protocol.StateManager) error { - if _, err := sm.PutState(s.cur, - protocol.KeyOption(voteViewKey), - protocol.NamespaceOption(s.namespace()), - protocol.SecondaryOnlyOption(), - ); err != nil { - return errors.Wrap(err, "failed to put candidate votes state") - } - return nil -} - -func (s *voteView) loadVotes(ctx context.Context, sr protocol.StateReader) error { - _, err := sr.State(s.cur, protocol.KeyOption(voteViewKey), protocol.NamespaceOption(s.namespace())) - if err != nil { - return errors.Wrap(err, "failed to get candidate votes state") - } - return nil -} - -func (s *voteView) namespace() string { - return voteViewNSPrefix + s.contractAddr.String() + return s.store.Store(ctx, sm, s.cur) } From b7974503a0d7cbc069f7ea92a6b296a3d17b8549 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 11:50:53 +0800 Subject: [PATCH 23/34] candidate votes for bucket type indexer --- blockindex/contractstaking/bucket.go | 26 +++ blockindex/contractstaking/event_handler.go | 130 +++++++++++++ .../contractstaking/eventprocessor_builder.go | 23 +++ blockindex/contractstaking/indexer.go | 91 ++++------ blockindex/contractstaking/stakeview.go | 171 ------------------ 5 files changed, 216 insertions(+), 225 deletions(-) create mode 100644 blockindex/contractstaking/event_handler.go create mode 100644 blockindex/contractstaking/eventprocessor_builder.go delete mode 100644 blockindex/contractstaking/stakeview.go diff --git a/blockindex/contractstaking/bucket.go b/blockindex/contractstaking/bucket.go index c2e0f6c453..6cfed464eb 100644 --- a/blockindex/contractstaking/bucket.go +++ b/blockindex/contractstaking/bucket.go @@ -7,6 +7,7 @@ package contractstaking import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) // Bucket defines the bucket struct for contract staking @@ -31,3 +32,28 @@ func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType, contractAddr s } return &vb } + +func assembleContractBucket(bi *bucketInfo, bt *BucketType) *contractstaking.Bucket { + return &contractstaking.Bucket{ + Candidate: bi.Delegate, + Owner: bi.Owner, + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: bi.CreatedAt, + UnlockedAt: bi.UnlockedAt, + UnstakedAt: bi.UnstakedAt, + } +} + +func contractBucketToVoteBucket(token uint64, b *contractstaking.Bucket, contractAddr string, blocksToDurationFn blocksDurationFn) *Bucket { + return assembleBucket(token, &bucketInfo{ + Owner: b.Owner, + Delegate: b.Candidate, + CreatedAt: b.CreatedAt, + UnlockedAt: b.UnlockedAt, + UnstakedAt: b.UnstakedAt, + }, &BucketType{ + Amount: b.StakedAmount, + Duration: b.StakedDuration, + }, contractAddr, blocksToDurationFn) +} diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go new file mode 100644 index 0000000000..d5c4dafb74 --- /dev/null +++ b/blockindex/contractstaking/event_handler.go @@ -0,0 +1,130 @@ +package contractstaking + +import ( + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex" +) + +type eventHandlerFactory struct { + store db.KVStore + calculateVoteWeightFunc stakingindex.CalculateUnmutedVoteWeightFn +} + +func newEventHandlerFactory(store db.KVStore, fn stakingindex.CalculateUnmutedVoteWeightFn) *eventHandlerFactory { + return &eventHandlerFactory{ + store: store, + calculateVoteWeightFunc: fn, + } +} + +func (f *eventHandlerFactory) NewEventHandler(v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { + store := newStoreWithBuffer(newStoreEventReader(f.store)) + handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.calculateVoteWeightFunc) + if err != nil { + return nil, err + } + return handler, nil +} + +func (f *eventHandlerFactory) NewEventHandlerWithStore(store stakingindex.BucketStore, v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { + handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.calculateVoteWeightFunc) + if err != nil { + return nil, err + } + return handler, nil +} + +type readOnlyEventHandler interface { + DeductBucket(address.Address, uint64) (*contractstaking.Bucket, error) +} + +type storeWithBuffer struct { + underlying readOnlyEventHandler + buffer map[string]map[uint64]*contractstaking.Bucket +} + +func newStoreWithBuffer(underlying readOnlyEventHandler) *storeWithBuffer { + return &storeWithBuffer{ + underlying: underlying, + buffer: make(map[string]map[uint64]*contractstaking.Bucket), + } +} + +func (e *storeWithBuffer) PutBucketType(address.Address, *contractstaking.BucketType) error { + // nothing to do + return nil +} + +func (e *storeWithBuffer) DeductBucket(addr address.Address, id uint64) (*contractstaking.Bucket, error) { + if bm, ok := e.buffer[addr.String()]; ok { + if bkt, ok := bm[id]; ok { + if bkt != nil { + return bkt, nil + } else { + return nil, errors.Wrap(contractstaking.ErrBucketNotExist, "bucket has been deleted") + } + } + } + return e.underlying.DeductBucket(addr, id) +} +func (e *storeWithBuffer) PutBucket(addr address.Address, id uint64, bucket *contractstaking.Bucket) error { + if bm, ok := e.buffer[addr.String()]; ok { + bm[id] = bucket + } else { + e.buffer[addr.String()] = map[uint64]*contractstaking.Bucket{id: bucket} + } + return nil +} +func (e *storeWithBuffer) DeleteBucket(addr address.Address, id uint64) error { + if bm, ok := e.buffer[addr.String()]; ok { + bm[id] = nil + } else { + e.buffer[addr.String()] = map[uint64]*contractstaking.Bucket{id: nil} + } + return nil +} + +type storeEventReader struct { + store db.KVStore +} + +func newStoreEventReader(store db.KVStore) *storeEventReader { + return &storeEventReader{store: store} +} + +func (e *storeEventReader) DeductBucket(addr address.Address, id uint64) (*contractstaking.Bucket, error) { + // load bucket info + value, err := e.store.Get(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id)) + switch errors.Cause(err) { + case nil: + case db.ErrNotExist, db.ErrBucketNotExist: + return nil, errors.Wrap(contractstaking.ErrBucketNotExist, "bucket doesn't exist") + default: + return nil, errors.Wrap(err, "failed to get bucket from db") + } + var bi bucketInfo + if err := bi.Deserialize(value); err != nil { + return nil, errors.Wrap(err, "failed to deserialize bucket info") + } + + // load bucket type + value, err = e.store.Get(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(bi.TypeIndex)) + switch errors.Cause(err) { + case nil: + case db.ErrNotExist, db.ErrBucketNotExist: + // should not happen + return nil, errors.Wrap(err, "bucket type doesn't exist but bucket exists, SHOULD NOT HAPPEN") + default: + return nil, errors.Wrap(err, "failed to get bucket type from db") + } + var bt BucketType + if err := bt.Deserialize(value); err != nil { + return nil, err + } + return assembleContractBucket(&bi, &bt), nil +} diff --git a/blockindex/contractstaking/eventprocessor_builder.go b/blockindex/contractstaking/eventprocessor_builder.go new file mode 100644 index 0000000000..7127ea83dc --- /dev/null +++ b/blockindex/contractstaking/eventprocessor_builder.go @@ -0,0 +1,23 @@ +package contractstaking + +import ( + "context" + + "github.com/iotexproject/iotex-address/address" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" +) + +type eventProcessorBuilder struct { + contractAddr address.Address +} + +func newEventProcessorBuilder(contractAddr address.Address) *eventProcessorBuilder { + return &eventProcessorBuilder{ + contractAddr: contractAddr, + } +} + +func (b *eventProcessorBuilder) Build(ctx context.Context, handler staking.EventHandler) staking.EventProcessor { + return newContractStakingEventProcessor(b.contractAddr, handler) +} diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 4779077095..fe65ad8fa5 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -21,6 +21,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex" ) const ( @@ -103,6 +104,14 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s if err != nil { return nil, errors.Wrapf(err, "failed to get height for contract %s", s.contractAddr) } + cfg := &stakingindex.VoteViewConfig{ + ContractAddr: s.contractAddr, + StartHeight: s.config.ContractDeployHeight, + } + calculateUnmutedVoteWeight := func(b *contractstaking.Bucket) *big.Int { + vb := contractBucketToVoteBucket(0, b, s.contractAddr.String(), s.genBlockDurationFn(s.height)) + return s.config.CalculateVoteWeight(vb) + } // contract staking state have not been initialized in state reader, we need to read from index if cssrHeight == 0 { if !s.IsReady() { @@ -110,65 +119,39 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s return nil, err } } - return &stakeView{ - contractAddr: s.contractAddr, - config: s.config, - cache: s.cache.Clone(), - height: s.height, - genBlockDurationFn: s.genBlockDurationFn, - }, nil + ids, typs, infos := s.cache.Buckets() + buckets := make(map[uint64]*contractstaking.Bucket) + for i, id := range ids { + buckets[id] = assembleContractBucket(infos[i], typs[i]) + } + cur := stakingindex.AggregateCandidateVotes(buckets, calculateUnmutedVoteWeight) + builder := newEventHandlerFactory(s.kvstore, calculateUnmutedVoteWeight) + processorBuilder := newEventProcessorBuilder(s.contractAddr) + mgr := stakingindex.NewCandidateVotesManager(s.contractAddr) + return stakingindex.NewVoteView(cfg, s.height, cur, builder, processorBuilder, s, mgr), nil } // otherwise, we need to read from state reader - tids, types, err := cssr.BucketTypes(s.contractAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to get bucket types for contract %s", s.contractAddr) - } - if len(tids) != len(types) { - return nil, errors.Errorf("length of tids (%d) does not match length of types (%d)", len(tids), len(types)) - } - ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(s.contractAddr) + cache := stakingindex.NewContractBucketCache(s.contractAddr, cssr) + builder := stakingindex.NewContractEventHandlerFactory(cssr, calculateUnmutedVoteWeight) + processorBuilder := newEventProcessorBuilder(s.contractAddr) + mgr := stakingindex.NewCandidateVotesManager(s.contractAddr) + cur, err := mgr.Load(ctx, sr) if err != nil { - return nil, errors.Wrapf(err, "failed to get buckets for contract %s", s.contractAddr) - } - if len(ids) != len(buckets) { - return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) - } - cache := &contractStakingCache{} - for i, id := range tids { - if types[i] == nil { - return nil, errors.Errorf("bucket type %d is nil", id) - } - cache.PutBucketType(id, types[i]) + return nil, err } + return stakingindex.NewVoteView(cfg, cssrHeight, cur, builder, processorBuilder, cache, mgr), nil +} + +func (s *Indexer) ContractStakingBuckets() (uint64, map[uint64]*contractstaking.Bucket, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + ids, typs, infos := s.cache.Buckets() + res := make(map[uint64]*contractstaking.Bucket) for i, id := range ids { - if buckets[i] == nil { - return nil, errors.New("bucket is nil") - } - tid, bt := cache.MatchBucketType(buckets[i].StakedAmount, buckets[i].StakedDuration) - if bt == nil { - return nil, errors.Errorf( - "no bucket type found for bucket %d with staked amount %s and duration %d", - id, - buckets[i].StakedAmount.String(), - buckets[i].StakedDuration, - ) - } - cache.PutBucketInfo(id, &bucketInfo{ - TypeIndex: tid, - CreatedAt: buckets[i].CreatedAt, - UnlockedAt: buckets[i].UnlockedAt, - UnstakedAt: buckets[i].UnstakedAt, - Delegate: buckets[i].Candidate, - Owner: buckets[i].Owner, - }) - } - - return &stakeView{ - cache: cache, - height: cssrHeight, - config: s.config, - contractAddr: s.contractAddr, - }, nil + res[id] = assembleContractBucket(infos[i], typs[i]) + } + return s.height, res, nil } func (s *Indexer) start(ctx context.Context) error { diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go deleted file mode 100644 index df69b2aec2..0000000000 --- a/blockindex/contractstaking/stakeview.go +++ /dev/null @@ -1,171 +0,0 @@ -package contractstaking - -import ( - "context" - "math/big" - "slices" - - "github.com/iotexproject/iotex-address/address" - "github.com/pkg/errors" - - "github.com/iotexproject/iotex-core/v2/action" - "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking" - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" -) - -type stakeView struct { - contractAddr address.Address - config Config - cache stakingCache - genBlockDurationFn func(view uint64) blocksDurationFn - height uint64 -} - -func (s *stakeView) Height() uint64 { - return s.height -} - -func (s *stakeView) Wrap() staking.ContractStakeView { - return &stakeView{ - contractAddr: s.contractAddr, - config: s.config, - cache: newWrappedCache(s.cache), - height: s.height, - genBlockDurationFn: s.genBlockDurationFn, - } -} - -func (s *stakeView) Fork() staking.ContractStakeView { - return &stakeView{ - contractAddr: s.contractAddr, - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, - genBlockDurationFn: s.genBlockDurationFn, - } -} - -func (s *stakeView) assembleBuckets(ids []uint64, types []*BucketType, infos []*bucketInfo) []*Bucket { - vbs := make([]*Bucket, 0, len(ids)) - for i, id := range ids { - bt := types[i] - info := infos[i] - if bt != nil && info != nil { - vbs = append(vbs, s.assembleBucket(id, info, bt)) - } - } - return vbs -} - -func (s *stakeView) IsDirty() bool { - return s.cache.IsDirty() -} - -func (s *stakeView) Migrate(handler staking.EventHandler) error { - bts := s.cache.BucketTypes() - tids := make([]uint64, 0, len(bts)) - for id := range bts { - tids = append(tids, id) - } - slices.Sort(tids) - for _, id := range tids { - if err := handler.PutBucketType(s.contractAddr, bts[id]); err != nil { - return err - } - } - ids, types, infos := s.cache.Buckets() - bucketMap := make(map[uint64]*bucketInfo, len(ids)) - typeMap := make(map[uint64]*BucketType, len(ids)) - for i, id := range ids { - bucketMap[id] = infos[i] - typeMap[id] = types[i] - } - slices.Sort(ids) - for _, id := range ids { - info, ok := bucketMap[id] - if !ok { - continue - } - bt := typeMap[id] - if err := handler.PutBucket(s.contractAddr, id, &contractstaking.Bucket{ - Candidate: info.Delegate, - Owner: info.Owner, - StakedAmount: bt.Amount, - StakedDuration: bt.Duration, - CreatedAt: info.CreatedAt, - UnstakedAt: info.UnstakedAt, - UnlockedAt: info.UnlockedAt, - Muted: false, - IsTimestampBased: false, - }); err != nil { - return err - } - } - return nil -} - -func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { - ids, types, infos := s.cache.BucketsByCandidate(candidate) - return s.assembleBuckets(ids, types, infos), nil -} - -func (s *stakeView) CandidateStakeVotes(ctx context.Context, candidate address.Address) *big.Int { - return nil -} - -func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { - return assembleBucket(token, bi, bt, s.contractAddr.String(), s.genBlockDurationFn(s.height)) -} - -func (s *stakeView) CreatePreStates(ctx context.Context) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - s.height = blkCtx.BlockHeight - return nil -} - -func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { - // new event handler for this receipt - handler := newContractStakingDirty(newWrappedCache(s.cache)) - processor := newContractStakingEventProcessor(s.contractAddr, handler) - if err := processor.ProcessReceipts(ctx, receipt); err != nil { - return err - } - _, delta := handler.Finalize() - s.cache = delta - - return nil -} - -func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { - cache, err := s.cache.Commit(ctx, s.contractAddr, sm) - if err != nil { - return err - } - s.cache = cache - return nil -} - -func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt, _ staking.EventHandler) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - height := blkCtx.BlockHeight - expectHeight := s.height + 1 - if expectHeight < s.config.ContractDeployHeight { - expectHeight = s.config.ContractDeployHeight - } - if height < expectHeight { - return nil - } - if height > expectHeight { - return errors.Errorf("invalid block height %d, expect %d", height, expectHeight) - } - - handler := newContractStakingDirty(newWrappedCache(s.cache)) - processor := newContractStakingEventProcessor(s.contractAddr, handler) - if err := processor.ProcessReceipts(ctx, receipts...); err != nil { - return err - } - _, delta := handler.Finalize() - s.cache = delta - s.height = height - return nil -} From e8c02f69223f843bcc21cb3a2aadbd904ffda0be Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 14:54:20 +0800 Subject: [PATCH 24/34] vote view store as contract --- .../stakingindex/candidate_votes.go | 20 +++++++++++++++++++ systemcontracts/systemcontracts.go | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go index c6a8240bd5..16ddd91ed8 100644 --- a/systemcontractindex/stakingindex/candidate_votes.go +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -6,8 +6,12 @@ import ( "github.com/gogo/protobuf/proto" "github.com/pkg/errors" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex/stakingpb" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) type CandidateVotes interface { @@ -32,6 +36,8 @@ type candidateVotes struct { cands map[string]*candidate } +var _ state.ContractStorageStandard = (*candidateVotes)(nil) + type candidateVotesWraper struct { base CandidateVotes change *candidateVotes @@ -123,6 +129,20 @@ func (cv *candidateVotes) Base() CandidateVotes { return cv } +func (cv *candidateVotes) ContractStorageAddress(ns string, key []byte) (address.Address, error) { + return systemcontracts.SystemContracts[systemcontracts.StakingViewContractIndex].Address, nil +} + +func (cv *candidateVotes) New() state.ContractStorageStandard { + return &candidateVotes{ + cands: make(map[string]*candidate), + } +} + +func (cv *candidateVotes) ContractStorageProxy() state.ContractStorage { + return state.NewContractStorageNamespacedWrapper(cv) +} + func newCandidateVotes() *candidateVotes { return &candidateVotes{ cands: make(map[string]*candidate), diff --git a/systemcontracts/systemcontracts.go b/systemcontracts/systemcontracts.go index cf5328374a..3f340d5e00 100644 --- a/systemcontracts/systemcontracts.go +++ b/systemcontracts/systemcontracts.go @@ -50,6 +50,8 @@ const ( RewardingContractV2Index // StakingContractIndex is the system contract for staking view storage StakingContractIndex + // StakingViewContractIndex is the system contract for staking view storage + StakingViewContractIndex // SystemContractCount is the total number of system contracts SystemContractCount ) @@ -60,7 +62,8 @@ const ( ) var systemContractTypes = map[int]int{ - StakingContractIndex: namespaceStorageContractType, + StakingContractIndex: namespaceStorageContractType, + StakingViewContractIndex: namespaceStorageContractType, } // SystemContracts holds all system contracts From 7d6731cf181863a39a9412e4b3ad33fe4912315c Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 18:05:38 +0800 Subject: [PATCH 25/34] fix ws dead lock --- state/factory/workingsetstore.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/state/factory/workingsetstore.go b/state/factory/workingsetstore.go index 6e9b4ec6c7..f733f6365a 100644 --- a/state/factory/workingsetstore.go +++ b/state/factory/workingsetstore.go @@ -91,8 +91,6 @@ func (store *stateDBWorkingSetStore) Put(ns string, key []byte, value []byte) er } func (store *stateDBWorkingSetStore) putKV(ns string, key []byte, value []byte) error { - store.lock.Lock() - defer store.lock.Unlock() if err := store.flusher.KVStoreWithBuffer().Put(ns, key, value); err != nil { return errors.Wrap(err, "failed to put value") } From ec88710dcd60ee39289011cda16f35a6de3ac025 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 18:06:04 +0800 Subject: [PATCH 26/34] fix systemcontractindex --- .../stakingindex/candidate_votes.go | 12 +++-- .../stakingindex/event_handler_factory.go | 19 +++++-- systemcontractindex/stakingindex/index.go | 20 +++++-- systemcontractindex/stakingindex/voteview.go | 53 +++++++++++++------ 4 files changed, 75 insertions(+), 29 deletions(-) diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go index 16ddd91ed8..112f76f424 100644 --- a/systemcontractindex/stakingindex/candidate_votes.go +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -72,10 +72,14 @@ func (cv *candidateVotes) IsDirty() bool { } func (cv *candidateVotes) Votes(fCtx protocol.FeatureCtx, cand string) *big.Int { + c := cv.cands[cand] + if c == nil { + return nil + } if !fCtx.FixContractStakingWeightedVotes { - return cv.cands[cand].amount + return c.amount } - return cv.cands[cand].votes + return c.votes } func (cv *candidateVotes) Add(cand string, amount *big.Int, votes *big.Int) { @@ -194,11 +198,11 @@ func (cv *candidateVotesWraper) Commit() { } func (cv *candidateVotesWraper) Serialize() ([]byte, error) { - return nil, errors.New("not implemented") + return cv.base.Serialize() } func (cv *candidateVotesWraper) Deserialize(data []byte) error { - return errors.New("not implemented") + return cv.base.Deserialize(data) } func (cv *candidateVotesWraper) Base() CandidateVotes { diff --git a/systemcontractindex/stakingindex/event_handler_factory.go b/systemcontractindex/stakingindex/event_handler_factory.go index 89cb40a0eb..4285c8a7a7 100644 --- a/systemcontractindex/stakingindex/event_handler_factory.go +++ b/systemcontractindex/stakingindex/event_handler_factory.go @@ -25,10 +25,19 @@ func (f *eventHandlerFactory) NewEventHandler(view CandidateVotes) (BucketStore, } func (f *eventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes) (BucketStore, error) { - if storer, ok := handler.(interface{ KVStore() db.KVStore }); ok { - return newVoteViewEventHandler(view, f.fn, f.bucketNS, storer.KVStore()) + veh, ok := handler.(*voteViewEventHandler) + if !ok { + return nil, errors.Errorf("handler %T is not voteViewEventHandler", handler) } - return nil, errors.New("handler does not support KVStore()") + store, ok := veh.BucketStore.(*storeWithBuffer) + if !ok { + return nil, errors.Errorf("handler %T is not storeWithBuffer", handler) + } + return newVoteViewEventHandler(view, f.fn, f.bucketNS, store.KVStore()) +} + +func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes) (BucketStore, error) { + return newVoteViewEventHandlerWraper(handler, view, f.fn) } type contractEventHandlerFactory struct { @@ -52,3 +61,7 @@ func (f *contractEventHandlerFactory) NewEventHandlerWithStore(handler BucketSto store := NewStoreWrapper(handler) return newVoteViewEventHandlerWraper(store, view, f.fn) } + +func (f *contractEventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes) (BucketStore, error) { + return newVoteViewEventHandlerWraper(handler, view, f.fn) +} diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index c12c947324..fa8209b767 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -16,6 +16,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontractindex" ) @@ -144,9 +145,14 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s contractAddr := s.common.ContractAddress() csr := contractstaking.NewStateReader(sr) - csrHeight, err := csr.Height(contractAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to get height for contract %s", contractAddr) + hasContractState := false + _, err := csr.NumOfBuckets(contractAddr) + switch errors.Cause(err) { + case nil: + hasContractState = true + case state.ErrStateNotExist: + default: + return nil, errors.Wrapf(err, "failed to get num of buckets for contract %s", contractAddr) } cfg := &VoteViewConfig{ ContractAddr: s.common.ContractAddress(), @@ -155,7 +161,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s Timestamped: s.timestamped, } // contract staking state have not been initialized in state reader, we need to read from index - if csrHeight == 0 { + if !hasContractState { if !s.common.Started() { if err := s.start(ctx); err != nil { return nil, err @@ -175,7 +181,11 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s if err != nil { return nil, err } - vv := NewVoteView(cfg, csrHeight, cur, builder, processorBuilder, cache, mgr) + srHeight, err := sr.Height() + if err != nil { + return nil, errors.Wrap(err, "failed to get state reader height") + } + vv := NewVoteView(cfg, srHeight, cur, builder, processorBuilder, cache, mgr) return vv, nil } diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go index 359079c4ac..dc186c048f 100644 --- a/systemcontractindex/stakingindex/voteview.go +++ b/systemcontractindex/stakingindex/voteview.go @@ -27,6 +27,7 @@ type ( EventHandlerFactory interface { NewEventHandler(CandidateVotes) (BucketStore, error) NewEventHandlerWithStore(BucketStore, CandidateVotes) (BucketStore, error) + NewEventHandlerWithHandler(BucketStore, CandidateVotes) (BucketStore, error) } voteView struct { @@ -73,31 +74,45 @@ func (s *voteView) Height() uint64 { func (s *voteView) Wrap() staking.ContractStakeView { cur := newCandidateVotesWrapper(s.cur) - handler, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) - if err != nil { - panic(errors.Wrap(err, "failed to wrap vote view event handler")) + var handler BucketStore + if s.handler != nil { + h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + if err != nil { + panic(errors.Wrap(err, "failed to wrap vote view event handler")) + } + handler = h } return &voteView{ - config: s.config, - height: s.height, - cur: cur, - handler: handler, - bucketCache: s.bucketCache, + config: s.config, + height: s.height, + cur: cur, + handler: handler, + bucketCache: s.bucketCache, + store: s.store, + handlerBuilder: s.handlerBuilder, + processorBuilder: s.processorBuilder, } } func (s *voteView) Fork() staking.ContractStakeView { cur := newCandidateVotesWrapperCommitInClone(s.cur) - handler, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) - if err != nil { - panic(errors.Wrap(err, "failed to fork vote view event handler")) + var handler BucketStore + if s.handler != nil { + h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + if err != nil { + panic(errors.Wrap(err, "failed to fork vote view event handler")) + } + handler = h } return &voteView{ - config: s.config, - height: s.height, - cur: cur, - handler: handler, - bucketCache: s.bucketCache, + config: s.config, + height: s.height, + cur: cur, + handler: handler, + bucketCache: s.bucketCache, + store: s.store, + handlerBuilder: s.handlerBuilder, + processorBuilder: s.processorBuilder, } } @@ -153,8 +168,12 @@ func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Rece if height != s.height+1 && height != s.config.StartHeight { return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) } + store, err := s.handlerBuilder.NewEventHandlerWithHandler(handler, s.cur) + if err != nil { + return errors.Wrap(err, "failed to create event handler with existing handler") + } ctx = protocol.WithBlockCtx(ctx, blkCtx) - if err := s.processorBuilder.Build(ctx, s.handler).ProcessReceipts(ctx, receipts...); err != nil { + if err := s.processorBuilder.Build(ctx, store).ProcessReceipts(ctx, receipts...); err != nil { return errors.Wrapf(err, "failed to handle receipts at height %d", height) } s.height = height From 752cc01202455d693c89707e3d5b567019cb596c Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 18:06:28 +0800 Subject: [PATCH 27/34] fix blockindex --- blockindex/contractstaking/event_handler.go | 4 ++++ blockindex/contractstaking/indexer.go | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go index d5c4dafb74..5839232a5a 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/event_handler.go @@ -39,6 +39,10 @@ func (f *eventHandlerFactory) NewEventHandlerWithStore(store stakingindex.Bucket return handler, nil } +func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler stakingindex.BucketStore, v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { + return stakingindex.NewVoteViewEventHandlerWraper(handler, v, f.calculateVoteWeightFunc) +} + type readOnlyEventHandler interface { DeductBucket(address.Address, uint64) (*contractstaking.Bucket, error) } diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index fe65ad8fa5..e2e7cdf48e 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -21,6 +21,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex" ) @@ -100,9 +101,14 @@ func (s *Indexer) CreateMemoryEventHandler(ctx context.Context) staking.EventHan // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { cssr := contractstaking.NewStateReader(sr) - cssrHeight, err := cssr.Height(s.contractAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to get height for contract %s", s.contractAddr) + _, err := cssr.NumOfBuckets(s.contractAddr) + hasContractState := false + switch errors.Cause(err) { + case nil: + hasContractState = true + case state.ErrStateNotExist: + default: + return nil, errors.Wrapf(err, "failed to get num of buckets for contract %s", s.contractAddr) } cfg := &stakingindex.VoteViewConfig{ ContractAddr: s.contractAddr, @@ -113,7 +119,7 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s return s.config.CalculateVoteWeight(vb) } // contract staking state have not been initialized in state reader, we need to read from index - if cssrHeight == 0 { + if !hasContractState { if !s.IsReady() { if err := s.start(ctx); err != nil { return nil, err @@ -139,7 +145,11 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s if err != nil { return nil, err } - return stakingindex.NewVoteView(cfg, cssrHeight, cur, builder, processorBuilder, cache, mgr), nil + srHeight, err := sr.Height() + if err != nil { + return nil, errors.Wrap(err, "failed to get state reader height") + } + return stakingindex.NewVoteView(cfg, srHeight, cur, builder, processorBuilder, cache, mgr), nil } func (s *Indexer) ContractStakingBuckets() (uint64, map[uint64]*contractstaking.Bucket, error) { From 5d9c31c63c94d766673252bd6ceedf5a08cc8b51 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 18:07:08 +0800 Subject: [PATCH 28/34] fix stuff --- action/protocol/staking/stakeview_builder.go | 5 +++++ chainservice/builder.go | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go index f6be1591e2..0bcd3e8535 100644 --- a/action/protocol/staking/stakeview_builder.go +++ b/action/protocol/staking/stakeview_builder.go @@ -47,6 +47,11 @@ func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateR if b.blockdao == nil { return nil, errors.Errorf("blockdao is nil, cannot build view for height %d", height) } + if starter, ok := b.indexer.(interface{ StartHeight() uint64 }); ok { + if viewHeight < starter.StartHeight() { + return view, nil + } + } handler := b.indexer.CreateMemoryEventHandler(ctx) for h := viewHeight + 1; h <= height; h++ { receipts, err := b.blockdao.GetReceipts(h) diff --git a/chainservice/builder.go b/chainservice/builder.go index d9de05b248..077a39f051 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -679,6 +679,10 @@ func (builder *Builder) registerStakingProtocol() error { if builder.cs.contractStakingIndexerV3 != nil { opts = append(opts, staking.WithContractStakingIndexerV3(builder.cs.contractStakingIndexerV3)) } + var indexerV1 staking.ContractStakingIndexerWithBucketType + if builder.cs.contractStakingIndexer != nil { + indexerV1 = builder.cs.contractStakingIndexer + } opts = append(opts, staking.WithBlockStore(builder.cs.blockdao)) stakingProtocol, err := staking.NewProtocol( staking.HelperCtx{ @@ -701,7 +705,7 @@ func (builder *Builder) registerStakingProtocol() error { }, builder.blocksToDurationFn, nil, - builder.cs.contractStakingIndexer, + indexerV1, builder.cs.contractStakingIndexerV2, opts..., ) From 4622115b3999fc8f129c1c7027200c344e276a75 Mon Sep 17 00:00:00 2001 From: envestcc Date: Tue, 9 Sep 2025 22:53:33 +0800 Subject: [PATCH 29/34] fix bugs --- api/coreservice.go | 7 ++++--- blockindex/contractstaking/indexer.go | 15 +++++++-------- chainservice/builder.go | 11 ++++++----- systemcontractindex/stakingindex/bucket.go | 5 +++++ systemcontractindex/stakingindex/cache.go | 6 +++++- systemcontractindex/stakingindex/index.go | 22 ++++++++++++++-------- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/api/coreservice.go b/api/coreservice.go index ba82512c94..36c442f390 100644 --- a/api/coreservice.go +++ b/api/coreservice.go @@ -2171,6 +2171,10 @@ func (core *coreService) simulateExecution( err error ws protocol.StateManagerWithCloser ) + ctx = genesis.WithGenesisContext(ctx, core.Genesis()) + ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: height, + })) if archive { ctx, err = core.bc.ContextAtHeight(ctx, height) if err != nil { @@ -2193,9 +2197,6 @@ func (core *coreService) simulateExecution( return nil, nil, status.Error(codes.InvalidArgument, err.Error()) } var pendingNonce uint64 - ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ - BlockHeight: height, - })) bcCtx := protocol.MustGetBlockchainCtx(ctx) if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount { pendingNonce = state.PendingNonceConsideringFreshAccount() diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index e2e7cdf48e..675bd73fa5 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -100,9 +100,15 @@ func (s *Indexer) CreateMemoryEventHandler(ctx context.Context) staking.EventHan // LoadStakeView loads the contract stake view func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { + if !s.IsReady() { + if err := s.start(ctx); err != nil { + return nil, err + } + } cssr := contractstaking.NewStateReader(sr) - _, err := cssr.NumOfBuckets(s.contractAddr) + mgr := stakingindex.NewCandidateVotesManager(s.contractAddr) hasContractState := false + cur, err := mgr.Load(ctx, sr) switch errors.Cause(err) { case nil: hasContractState = true @@ -120,11 +126,6 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s } // contract staking state have not been initialized in state reader, we need to read from index if !hasContractState { - if !s.IsReady() { - if err := s.start(ctx); err != nil { - return nil, err - } - } ids, typs, infos := s.cache.Buckets() buckets := make(map[uint64]*contractstaking.Bucket) for i, id := range ids { @@ -140,8 +141,6 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s cache := stakingindex.NewContractBucketCache(s.contractAddr, cssr) builder := stakingindex.NewContractEventHandlerFactory(cssr, calculateUnmutedVoteWeight) processorBuilder := newEventProcessorBuilder(s.contractAddr) - mgr := stakingindex.NewCandidateVotesManager(s.contractAddr) - cur, err := mgr.Load(ctx, sr) if err != nil { return nil, err } diff --git a/chainservice/builder.go b/chainservice/builder.go index 077a39f051..2fed233a0c 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -362,18 +362,19 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { dbConfig := builder.cfg.DB dbConfig.DbPath = builder.cfg.Chain.ContractStakingIndexDBPath kvstore := db.NewBoltDB(dbConfig) + voteCalcConsts := builder.cfg.Genesis.VoteWeightCalConsts + calculateVotesWeight := func(v *staking.VoteBucket) *big.Int { + return staking.CalculateVoteWeight(voteCalcConsts, v, false) + } // build contract staking indexer if builder.cs.contractStakingIndexer == nil && len(builder.cfg.Genesis.SystemStakingContractAddress) > 0 { - voteCalcConsts := builder.cfg.Genesis.VoteWeightCalConsts indexer, err := contractstaking.NewContractStakingIndexer( kvstore, contractstaking.Config{ ContractAddress: builder.cfg.Genesis.SystemStakingContractAddress, ContractDeployHeight: builder.cfg.Genesis.SystemStakingContractHeight, - CalculateVoteWeight: func(v *staking.VoteBucket) *big.Int { - return staking.CalculateVoteWeight(voteCalcConsts, v, false) - }, - BlocksToDuration: builder.blocksToDurationFn, + CalculateVoteWeight: calculateVotesWeight, + BlocksToDuration: builder.blocksToDurationFn, }) if err != nil { return err diff --git a/systemcontractindex/stakingindex/bucket.go b/systemcontractindex/stakingindex/bucket.go index 8a292912a0..4b491cacde 100644 --- a/systemcontractindex/stakingindex/bucket.go +++ b/systemcontractindex/stakingindex/bucket.go @@ -103,6 +103,11 @@ func (b *Bucket) Clone() *Bucket { } */ +// AssembleVoteBucket assembles a VoteBucket from a Bucket +func AssembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksToDurationFn blocksDurationFn) *VoteBucket { + return assembleVoteBucket(token, bkt, contractAddr, blocksToDurationFn) +} + func assembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksToDurationFn blocksDurationFn) *VoteBucket { vb := VoteBucket{ Index: token, diff --git a/systemcontractindex/stakingindex/cache.go b/systemcontractindex/stakingindex/cache.go index 8882f7c53e..254348c941 100644 --- a/systemcontractindex/stakingindex/cache.go +++ b/systemcontractindex/stakingindex/cache.go @@ -70,7 +70,7 @@ func (s *base) Load(kvstore db.KVStore, ns, bucketNS string) error { if err := b.Deserialize(vs[i]); err != nil { return err } - s.PutBucket(byteutil.BytesToUint64BigEndian(ks[i]), &b) + s.putBucket(byteutil.BytesToUint64BigEndian(ks[i]), &b) } return nil } @@ -104,6 +104,10 @@ func (s *base) Clone() indexerCache { func (s *base) PutBucket(id uint64, bkt *Bucket) { s.mu.Lock() defer s.mu.Unlock() + s.putBucket(id, bkt) +} + +func (s *base) putBucket(id uint64, bkt *Bucket) { cand := bkt.Candidate.String() if s.buckets[id] != nil { prevCand := s.buckets[id].Candidate.String() diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index fa8209b767..b49535c288 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -80,6 +80,13 @@ func EnableTimestamped() IndexerOption { } } +// WithCalculateUnmutedVoteWeightFn sets the function to calculate unmuted vote weight +func WithCalculateUnmutedVoteWeightFn(f CalculateUnmutedVoteWeightFn) IndexerOption { + return func(s *Indexer) { + s.calculateUnmutedVoteWeight = f + } +} + // NewIndexer creates a new staking indexer func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { bucketNS := contractAddr.String() + "#" + stakingBucketNS @@ -143,10 +150,16 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s s.mutex.RLock() defer s.mutex.RUnlock() + if !s.common.Started() { + if err := s.start(ctx); err != nil { + return nil, err + } + } contractAddr := s.common.ContractAddress() csr := contractstaking.NewStateReader(sr) hasContractState := false - _, err := csr.NumOfBuckets(contractAddr) + mgr := NewCandidateVotesManager(s.ContractAddress()) + cur, err := mgr.Load(ctx, sr) switch errors.Cause(err) { case nil: hasContractState = true @@ -162,11 +175,6 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s } // contract staking state have not been initialized in state reader, we need to read from index if !hasContractState { - if !s.common.Started() { - if err := s.start(ctx); err != nil { - return nil, err - } - } builder := NewEventHandlerFactory(s.bucketNS, s.common.KVStore()) processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) mgr := NewCandidateVotesManager(s.ContractAddress()) @@ -176,8 +184,6 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s cache := NewContractBucketCache(s.common.ContractAddress(), csr) builder := NewContractEventHandlerFactory(csr, s.calculateUnmutedVoteWeight) processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) - mgr := NewCandidateVotesManager(s.ContractAddress()) - cur, err := mgr.Load(ctx, sr) if err != nil { return nil, err } From 58556eabc970727965a437417f6d8bb60022314b Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 10 Sep 2025 13:19:26 +0800 Subject: [PATCH 30/34] fix systemcontractindex --- chainservice/builder.go | 12 ++++- .../stakingindex/candidate_votes.go | 19 ++++---- .../stakingindex/event_handler_factory.go | 47 +++++++++++------- systemcontractindex/stakingindex/index.go | 48 ++++++++++++------- .../stakingindex/vote_view_handler.go | 1 + systemcontractindex/stakingindex/voteview.go | 21 ++++---- 6 files changed, 93 insertions(+), 55 deletions(-) diff --git a/chainservice/builder.go b/chainservice/builder.go index 2fed233a0c..b6707dfebd 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -387,13 +387,17 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { if err != nil { return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV2Address) } - indexer := stakingindex.NewIndexer( + indexer, err := stakingindex.NewIndexer( kvstore, contractAddr, builder.cfg.Genesis.SystemStakingContractV2Height, builder.blocksToDurationFn, stakingindex.WithMuteHeight(builder.cfg.Genesis.WakeBlockHeight), + stakingindex.WithCalculateUnmutedVoteWeightFn(calculateVotesWeight), ) + if err != nil { + return errors.Wrapf(err, "failed to create contract staking indexer v2") + } builder.cs.contractStakingIndexerV2 = indexer } // build contract staking indexer v3 @@ -402,13 +406,17 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { if err != nil { return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV3Address) } - indexer := stakingindex.NewIndexer( + indexer, err := stakingindex.NewIndexer( kvstore, contractAddr, builder.cfg.Genesis.SystemStakingContractV3Height, builder.blocksToDurationFn, stakingindex.EnableTimestamped(), + stakingindex.WithCalculateUnmutedVoteWeightFn(calculateVotesWeight), ) + if err != nil { + return errors.Wrapf(err, "failed to create contract staking indexer v3") + } builder.cs.contractStakingIndexerV3 = indexer } return nil diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go index 112f76f424..dcc9d384f4 100644 --- a/systemcontractindex/stakingindex/candidate_votes.go +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -18,7 +18,7 @@ type CandidateVotes interface { Clone() CandidateVotes Votes(fCtx protocol.FeatureCtx, cand string) *big.Int Add(cand string, amount *big.Int, votes *big.Int) - Commit() + Commit() CandidateVotes Base() CandidateVotes IsDirty() bool Serialize() ([]byte, error) @@ -125,8 +125,8 @@ func (cv *candidateVotes) Deserialize(data []byte) error { return nil } -func (cv *candidateVotes) Commit() { - // nothing to do +func (cv *candidateVotes) Commit() CandidateVotes { + return cv } func (cv *candidateVotes) Base() CandidateVotes { @@ -187,22 +187,22 @@ func (cv *candidateVotesWraper) Add(cand string, amount *big.Int, votes *big.Int cv.change.Add(cand, amount, votes) } -func (cv *candidateVotesWraper) Commit() { +func (cv *candidateVotesWraper) Commit() CandidateVotes { // Commit the changes to the base for cand, change := range cv.change.cands { cv.base.Add(cand, change.amount, change.votes) } cv.change = newCandidateVotes() // base commit - cv.base.Commit() + return cv.base.Commit() } func (cv *candidateVotesWraper) Serialize() ([]byte, error) { - return cv.base.Serialize() + return nil, errors.New("not implemented") } func (cv *candidateVotesWraper) Deserialize(data []byte) error { - return cv.base.Deserialize(data) + return errors.New("not implemented") } func (cv *candidateVotesWraper) Base() CandidateVotes { @@ -221,8 +221,7 @@ func (cv *candidateVotesWraperCommitInClone) Clone() CandidateVotes { } } -func (cv *candidateVotesWraperCommitInClone) Commit() { +func (cv *candidateVotesWraperCommitInClone) Commit() CandidateVotes { cv.base = cv.base.Clone() - cv.candidateVotesWraper.Commit() - return + return cv.candidateVotesWraper.Commit() } diff --git a/systemcontractindex/stakingindex/event_handler_factory.go b/systemcontractindex/stakingindex/event_handler_factory.go index 4285c8a7a7..712fe89335 100644 --- a/systemcontractindex/stakingindex/event_handler_factory.go +++ b/systemcontractindex/stakingindex/event_handler_factory.go @@ -1,6 +1,8 @@ package stakingindex import ( + "math/big" + "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" @@ -10,21 +12,22 @@ import ( type eventHandlerFactory struct { bucketNS string store db.KVStore - fn CalculateUnmutedVoteWeightFn + fn CalculateUnmutedVoteWeightAtFn } -func NewEventHandlerFactory(bucketNS string, store db.KVStore) EventHandlerFactory { +func NewEventHandlerFactory(bucketNS string, store db.KVStore, fn CalculateUnmutedVoteWeightAtFn) EventHandlerFactory { return &eventHandlerFactory{ bucketNS: bucketNS, store: store, + fn: fn, } } -func (f *eventHandlerFactory) NewEventHandler(view CandidateVotes) (BucketStore, error) { - return newVoteViewEventHandler(view, f.fn, f.bucketNS, f.store) +func (f *eventHandlerFactory) NewEventHandler(view CandidateVotes, height uint64) (BucketStore, error) { + return newVoteViewEventHandler(view, f.genCalculateUnmutedVoteWeight(height), f.bucketNS, f.store) } -func (f *eventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes) (BucketStore, error) { +func (f *eventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes, height uint64) (BucketStore, error) { veh, ok := handler.(*voteViewEventHandler) if !ok { return nil, errors.Errorf("handler %T is not voteViewEventHandler", handler) @@ -33,35 +36,47 @@ func (f *eventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view if !ok { return nil, errors.Errorf("handler %T is not storeWithBuffer", handler) } - return newVoteViewEventHandler(view, f.fn, f.bucketNS, store.KVStore()) + return newVoteViewEventHandler(view, f.genCalculateUnmutedVoteWeight(height), f.bucketNS, store.KVStore()) +} + +func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes, height uint64) (BucketStore, error) { + return newVoteViewEventHandlerWraper(handler, view, f.genCalculateUnmutedVoteWeight(height)) } -func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes) (BucketStore, error) { - return newVoteViewEventHandlerWraper(handler, view, f.fn) +func (f *eventHandlerFactory) genCalculateUnmutedVoteWeight(height uint64) CalculateUnmutedVoteWeightFn { + return func(b *contractstaking.Bucket) *big.Int { + return f.fn(b, height) + } } type contractEventHandlerFactory struct { csr *contractstaking.ContractStakingStateReader - fn CalculateUnmutedVoteWeightFn + fn CalculateUnmutedVoteWeightAtFn } -func NewContractEventHandlerFactory(csr *contractstaking.ContractStakingStateReader, fn CalculateUnmutedVoteWeightFn) EventHandlerFactory { +func NewContractEventHandlerFactory(csr *contractstaking.ContractStakingStateReader, fn CalculateUnmutedVoteWeightAtFn) EventHandlerFactory { return &contractEventHandlerFactory{ csr: csr, fn: fn, } } -func (f *contractEventHandlerFactory) NewEventHandler(view CandidateVotes) (BucketStore, error) { +func (f *contractEventHandlerFactory) NewEventHandler(view CandidateVotes, height uint64) (BucketStore, error) { store := NewStoreWithContract(f.csr) - return newVoteViewEventHandlerWraper(store, view, f.fn) + return newVoteViewEventHandlerWraper(store, view, f.genCalculateUnmutedVoteWeight(height)) } -func (f *contractEventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes) (BucketStore, error) { +func (f *contractEventHandlerFactory) NewEventHandlerWithStore(handler BucketStore, view CandidateVotes, height uint64) (BucketStore, error) { store := NewStoreWrapper(handler) - return newVoteViewEventHandlerWraper(store, view, f.fn) + return newVoteViewEventHandlerWraper(store, view, f.genCalculateUnmutedVoteWeight(height)) } -func (f *contractEventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes) (BucketStore, error) { - return newVoteViewEventHandlerWraper(handler, view, f.fn) +func (f *contractEventHandlerFactory) NewEventHandlerWithHandler(handler BucketStore, view CandidateVotes, height uint64) (BucketStore, error) { + return newVoteViewEventHandlerWraper(handler, view, f.genCalculateUnmutedVoteWeight(height)) +} + +func (f *contractEventHandlerFactory) genCalculateUnmutedVoteWeight(height uint64) CalculateUnmutedVoteWeightFn { + return func(b *contractstaking.Bucket) *big.Int { + return f.fn(b, height) + } } diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index b49535c288..54c129ab11 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -2,6 +2,7 @@ package stakingindex import ( "context" + "math/big" "sync" "time" @@ -49,21 +50,22 @@ type ( } // Indexer is the staking indexer Indexer struct { - common *systemcontractindex.IndexerCommon - cache *base // in-memory cache, used to query index data - mutex sync.RWMutex - blocksToDuration blocksDurationAtFn // function to calculate duration from block range - bucketNS string - ns string - muteHeight uint64 - timestamped bool - calculateUnmutedVoteWeight CalculateUnmutedVoteWeightFn + common *systemcontractindex.IndexerCommon + cache *base // in-memory cache, used to query index data + mutex sync.RWMutex + blocksToDuration blocksDurationAtFn // function to calculate duration from block range + bucketNS string + ns string + muteHeight uint64 + timestamped bool + calculateVoteWeight CalculateVoteWeightFunc } // IndexerOption is the option to create an indexer IndexerOption func(*Indexer) - blocksDurationFn func(start uint64, end uint64) time.Duration - blocksDurationAtFn func(start uint64, end uint64, viewAt uint64) time.Duration + blocksDurationFn func(start uint64, end uint64) time.Duration + blocksDurationAtFn func(start uint64, end uint64, viewAt uint64) time.Duration + CalculateVoteWeightFunc func(v *VoteBucket) *big.Int ) // WithMuteHeight sets the mute height @@ -81,14 +83,14 @@ func EnableTimestamped() IndexerOption { } // WithCalculateUnmutedVoteWeightFn sets the function to calculate unmuted vote weight -func WithCalculateUnmutedVoteWeightFn(f CalculateUnmutedVoteWeightFn) IndexerOption { +func WithCalculateUnmutedVoteWeightFn(f CalculateVoteWeightFunc) IndexerOption { return func(s *Indexer) { - s.calculateUnmutedVoteWeight = f + s.calculateVoteWeight = f } } // NewIndexer creates a new staking indexer -func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { +func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) (*Indexer, error) { bucketNS := contractAddr.String() + "#" + stakingBucketNS ns := contractAddr.String() + "#" + stakingNS idx := &Indexer{ @@ -101,7 +103,10 @@ func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight ui for _, opt := range opts { opt(idx) } - return idx + if idx.calculateVoteWeight == nil { + return nil, errors.New("calculateVoteWeight function is not set") + } + return idx, nil } // Start starts the indexer @@ -175,14 +180,14 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s } // contract staking state have not been initialized in state reader, we need to read from index if !hasContractState { - builder := NewEventHandlerFactory(s.bucketNS, s.common.KVStore()) + builder := NewEventHandlerFactory(s.bucketNS, s.common.KVStore(), s.calculateContractVoteWeight) processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) mgr := NewCandidateVotesManager(s.ContractAddress()) return NewVoteView(cfg, s.common.Height(), s.createCandidateVotes(s.cache.buckets), builder, processorBuilder, s, mgr), nil } // otherwise, we need to read from state reader cache := NewContractBucketCache(s.common.ContractAddress(), csr) - builder := NewContractEventHandlerFactory(csr, s.calculateUnmutedVoteWeight) + builder := NewContractEventHandlerFactory(csr, s.calculateContractVoteWeight) processorBuilder := newEventProcessorBuilder(s.common.ContractAddress(), s.timestamped, s.muteHeight) if err != nil { return nil, err @@ -387,7 +392,14 @@ func (s *Indexer) genBlockDurationFn(view uint64) blocksDurationFn { } func (s *Indexer) createCandidateVotes(bkts map[uint64]*Bucket) CandidateVotes { - return AggregateCandidateVotes(bkts, s.calculateUnmutedVoteWeight) + return AggregateCandidateVotes(bkts, func(b *contractstaking.Bucket) *big.Int { + return s.calculateContractVoteWeight(b, s.common.Height()) + }) +} + +func (s *Indexer) calculateContractVoteWeight(b *Bucket, height uint64) *big.Int { + vb := assembleVoteBucket(0, b, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) + return s.calculateVoteWeight(vb) } // AggregateCandidateVotes aggregates the votes for each candidate from the given buckets diff --git a/systemcontractindex/stakingindex/vote_view_handler.go b/systemcontractindex/stakingindex/vote_view_handler.go index efbac3355a..aacbde907f 100644 --- a/systemcontractindex/stakingindex/vote_view_handler.go +++ b/systemcontractindex/stakingindex/vote_view_handler.go @@ -12,6 +12,7 @@ import ( ) type CalculateUnmutedVoteWeightFn func(*contractstaking.Bucket) *big.Int +type CalculateUnmutedVoteWeightAtFn func(*contractstaking.Bucket, uint64) *big.Int type voteViewEventHandler struct { BucketStore diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go index dc186c048f..6b7cb18a25 100644 --- a/systemcontractindex/stakingindex/voteview.go +++ b/systemcontractindex/stakingindex/voteview.go @@ -25,9 +25,9 @@ type ( Timestamped bool } EventHandlerFactory interface { - NewEventHandler(CandidateVotes) (BucketStore, error) - NewEventHandlerWithStore(BucketStore, CandidateVotes) (BucketStore, error) - NewEventHandlerWithHandler(BucketStore, CandidateVotes) (BucketStore, error) + NewEventHandler(CandidateVotes, uint64) (BucketStore, error) + NewEventHandlerWithStore(BucketStore, CandidateVotes, uint64) (BucketStore, error) + NewEventHandlerWithHandler(BucketStore, CandidateVotes, uint64) (BucketStore, error) } voteView struct { @@ -76,7 +76,7 @@ func (s *voteView) Wrap() staking.ContractStakeView { cur := newCandidateVotesWrapper(s.cur) var handler BucketStore if s.handler != nil { - h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur, s.height) if err != nil { panic(errors.Wrap(err, "failed to wrap vote view event handler")) } @@ -98,7 +98,7 @@ func (s *voteView) Fork() staking.ContractStakeView { cur := newCandidateVotesWrapperCommitInClone(s.cur) var handler BucketStore if s.handler != nil { - h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur) + h, err := s.handlerBuilder.NewEventHandlerWithStore(s.handler, cur, s.height) if err != nil { panic(errors.Wrap(err, "failed to fork vote view event handler")) } @@ -147,7 +147,7 @@ func (s *voteView) CandidateStakeVotes(ctx context.Context, candidate address.Ad func (s *voteView) CreatePreStates(ctx context.Context) error { blkCtx := protocol.MustGetBlockCtx(ctx) s.height = blkCtx.BlockHeight - handler, err := s.handlerBuilder.NewEventHandler(s.cur) + handler, err := s.handlerBuilder.NewEventHandler(s.cur, s.height) if err != nil { return err } @@ -168,7 +168,7 @@ func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Rece if height != s.height+1 && height != s.config.StartHeight { return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) } - store, err := s.handlerBuilder.NewEventHandlerWithHandler(handler, s.cur) + store, err := s.handlerBuilder.NewEventHandlerWithHandler(handler, s.cur, height) if err != nil { return errors.Wrap(err, "failed to create event handler with existing handler") } @@ -181,7 +181,10 @@ func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Rece } func (s *voteView) Commit(ctx context.Context, sm protocol.StateManager) error { - s.cur.Commit() - + dirty := s.cur.IsDirty() + s.cur = s.cur.Commit() + if !dirty { + return nil + } return s.store.Store(ctx, sm, s.cur) } From a853e6a6ce6cc406d3426864c8c2a2be7ea2783d Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 10 Sep 2025 13:19:36 +0800 Subject: [PATCH 31/34] fix blockindex --- blockindex/contractstaking/event_handler.go | 24 ++++++++++++++------- blockindex/contractstaking/indexer.go | 12 ++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go index 5839232a5a..37afeeb19a 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/event_handler.go @@ -1,6 +1,8 @@ package contractstaking import ( + "math/big" + "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" @@ -12,35 +14,41 @@ import ( type eventHandlerFactory struct { store db.KVStore - calculateVoteWeightFunc stakingindex.CalculateUnmutedVoteWeightFn + calculateVoteWeightFunc stakingindex.CalculateUnmutedVoteWeightAtFn } -func newEventHandlerFactory(store db.KVStore, fn stakingindex.CalculateUnmutedVoteWeightFn) *eventHandlerFactory { +func newEventHandlerFactory(store db.KVStore, fn stakingindex.CalculateUnmutedVoteWeightAtFn) *eventHandlerFactory { return &eventHandlerFactory{ store: store, calculateVoteWeightFunc: fn, } } -func (f *eventHandlerFactory) NewEventHandler(v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { +func (f *eventHandlerFactory) NewEventHandler(v stakingindex.CandidateVotes, height uint64) (stakingindex.BucketStore, error) { store := newStoreWithBuffer(newStoreEventReader(f.store)) - handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.calculateVoteWeightFunc) + handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.genCalculateUnmutedVoteWeight(height)) if err != nil { return nil, err } return handler, nil } -func (f *eventHandlerFactory) NewEventHandlerWithStore(store stakingindex.BucketStore, v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { - handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.calculateVoteWeightFunc) +func (f *eventHandlerFactory) NewEventHandlerWithStore(store stakingindex.BucketStore, v stakingindex.CandidateVotes, height uint64) (stakingindex.BucketStore, error) { + handler, err := stakingindex.NewVoteViewEventHandlerWraper(store, v, f.genCalculateUnmutedVoteWeight(height)) if err != nil { return nil, err } return handler, nil } -func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler stakingindex.BucketStore, v stakingindex.CandidateVotes) (stakingindex.BucketStore, error) { - return stakingindex.NewVoteViewEventHandlerWraper(handler, v, f.calculateVoteWeightFunc) +func (f *eventHandlerFactory) NewEventHandlerWithHandler(handler stakingindex.BucketStore, v stakingindex.CandidateVotes, height uint64) (stakingindex.BucketStore, error) { + return stakingindex.NewVoteViewEventHandlerWraper(handler, v, f.genCalculateUnmutedVoteWeight(height)) +} + +func (f *eventHandlerFactory) genCalculateUnmutedVoteWeight(height uint64) stakingindex.CalculateUnmutedVoteWeightFn { + return func(b *contractstaking.Bucket) *big.Int { + return f.calculateVoteWeightFunc(b, height) + } } type readOnlyEventHandler interface { diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 675bd73fa5..6af28ec2f9 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -120,8 +120,8 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s ContractAddr: s.contractAddr, StartHeight: s.config.ContractDeployHeight, } - calculateUnmutedVoteWeight := func(b *contractstaking.Bucket) *big.Int { - vb := contractBucketToVoteBucket(0, b, s.contractAddr.String(), s.genBlockDurationFn(s.height)) + calculateUnmutedVoteWeightAt := func(b *contractstaking.Bucket, height uint64) *big.Int { + vb := contractBucketToVoteBucket(0, b, s.contractAddr.String(), s.genBlockDurationFn(height)) return s.config.CalculateVoteWeight(vb) } // contract staking state have not been initialized in state reader, we need to read from index @@ -131,15 +131,17 @@ func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (s for i, id := range ids { buckets[id] = assembleContractBucket(infos[i], typs[i]) } - cur := stakingindex.AggregateCandidateVotes(buckets, calculateUnmutedVoteWeight) - builder := newEventHandlerFactory(s.kvstore, calculateUnmutedVoteWeight) + cur := stakingindex.AggregateCandidateVotes(buckets, func(b *contractstaking.Bucket) *big.Int { + return calculateUnmutedVoteWeightAt(b, s.height) + }) + builder := newEventHandlerFactory(s.kvstore, calculateUnmutedVoteWeightAt) processorBuilder := newEventProcessorBuilder(s.contractAddr) mgr := stakingindex.NewCandidateVotesManager(s.contractAddr) return stakingindex.NewVoteView(cfg, s.height, cur, builder, processorBuilder, s, mgr), nil } // otherwise, we need to read from state reader cache := stakingindex.NewContractBucketCache(s.contractAddr, cssr) - builder := stakingindex.NewContractEventHandlerFactory(cssr, calculateUnmutedVoteWeight) + builder := stakingindex.NewContractEventHandlerFactory(cssr, calculateUnmutedVoteWeightAt) processorBuilder := newEventProcessorBuilder(s.contractAddr) if err != nil { return nil, err From 113a1ecdc6fd056edaed408ec26c7bad32ea55d7 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 10 Sep 2025 19:50:42 +0800 Subject: [PATCH 32/34] fix ws --- state/factory/workingsetstore_erigon.go | 10 +++++----- state/factory/workingsetstore_erigon_simulate.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/state/factory/workingsetstore_erigon.go b/state/factory/workingsetstore_erigon.go index eae419861e..a7ee32cf0b 100644 --- a/state/factory/workingsetstore_erigon.go +++ b/state/factory/workingsetstore_erigon.go @@ -282,7 +282,7 @@ func (store *erigonWorkingSetStore) RevertSnapshot(sn int) error { func (store *erigonWorkingSetStore) ResetSnapshots() {} func (store *erigonWorkingSetStore) PutObject(ns string, key []byte, obj any, secondaryOnly bool) (err error) { - storage := store.objectContractStorage(obj) + storage := objectContractStorage(obj) if storage != nil { log.L().Debug("put object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj)), zap.Any("content", obj)) return storage.StoreToContract(ns, key, store.newContractBackend(store.ctx, store.intraBlockState, store.sr)) @@ -322,7 +322,7 @@ func (store *erigonWorkingSetStore) Put(ns string, key []byte, value []byte) (er } func (store *erigonWorkingSetStore) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { - storage := store.objectContractStorage(obj) + storage := objectContractStorage(obj) if storage != nil { defer func() { log.L().Debug("get object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) @@ -365,7 +365,7 @@ func (store *erigonWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { } func (store *erigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any, secondaryOnly bool) error { - storage := store.objectContractStorage(obj) + storage := objectContractStorage(obj) if storage != nil { log.L().Debug("delete object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) return storage.DeleteFromContract(ns, key, store.newContractBackend(store.ctx, store.intraBlockState, store.sr)) @@ -386,7 +386,7 @@ func (store *erigonWorkingSetStore) Filter(string, db.Condition, []byte, []byte) } func (store *erigonWorkingSetStore) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { - storage := store.objectContractStorage(obj) + storage := objectContractStorage(obj) if storage == nil { return nil, nil, errors.Wrapf(ErrNotSupported, "unsupported object type %T in ns %s", obj, ns) } @@ -469,7 +469,7 @@ func (store *erigonWorkingSetStore) newContractBackend(ctx context.Context, intr return NewContractBackend(store.intraBlockState, store.sr, blkCtx.BlockHeight, blkCtx.BlockTimeStamp, &g, bcCtx.EvmNetworkID) } -func (store *erigonWorkingSetStore) objectContractStorage(obj any) state.ContractStorage { +func objectContractStorage(obj any) state.ContractStorage { if cs, ok := obj.(state.ContractStorage); ok { return cs } diff --git a/state/factory/workingsetstore_erigon_simulate.go b/state/factory/workingsetstore_erigon_simulate.go index 61c35a3c59..e1cab0be0e 100644 --- a/state/factory/workingsetstore_erigon_simulate.go +++ b/state/factory/workingsetstore_erigon_simulate.go @@ -36,7 +36,7 @@ func (store *erigonWorkingSetStoreForSimulate) Stop(context.Context) error { } func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, obj any, secondaryOnly bool) error { - if _, ok := obj.(state.ContractStorage); ok { + if objectContractStorage(obj) != nil { return store.erigonStore.GetObject(ns, key, obj, secondaryOnly) } if _, ok := obj.(*state.Account); !ok && ns == AccountKVNamespace { @@ -51,7 +51,7 @@ func (store *erigonWorkingSetStoreForSimulate) GetObject(ns string, key []byte, } func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte, obj any, secondaryOnly bool) ([][]byte, [][]byte, error) { - if _, ok := obj.(state.ContractStorage); ok { + if objectContractStorage(obj) != nil { return store.erigonStore.States(ns, keys, obj, secondaryOnly) } // currently only used for staking & poll, no need to read from erigon From 086d4210a2d91f95801573f8359711400d0a64e2 Mon Sep 17 00:00:00 2001 From: envestcc Date: Wed, 10 Sep 2025 19:51:51 +0800 Subject: [PATCH 33/34] fix votes --- systemcontractindex/stakingindex/candidate_votes.go | 2 +- systemcontractindex/stakingindex/voteview.go | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go index dcc9d384f4..e1d5cb23b3 100644 --- a/systemcontractindex/stakingindex/candidate_votes.go +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -3,8 +3,8 @@ package stakingindex import ( "math/big" - "github.com/gogo/protobuf/proto" "github.com/pkg/errors" + "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-address/address" diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go index 6b7cb18a25..fe2e3c5c58 100644 --- a/systemcontractindex/stakingindex/voteview.go +++ b/systemcontractindex/stakingindex/voteview.go @@ -181,10 +181,6 @@ func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Rece } func (s *voteView) Commit(ctx context.Context, sm protocol.StateManager) error { - dirty := s.cur.IsDirty() s.cur = s.cur.Commit() - if !dirty { - return nil - } return s.store.Store(ctx, sm, s.cur) } From 464360719e089032cbe3e65eb70110927b6d9af9 Mon Sep 17 00:00:00 2001 From: zhi Date: Thu, 18 Sep 2025 13:00:31 +0800 Subject: [PATCH 34/34] unit test for nft event handler --- action/protocol/mock_protocol.go | 39 +++++ .../staking/contractstake_indexer_mock.go | 28 ++++ .../staking/contractstaking/statemanager.go | 2 +- .../protocol/staking/nfteventhandler_test.go | 144 ++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 action/protocol/staking/nfteventhandler_test.go diff --git a/action/protocol/mock_protocol.go b/action/protocol/mock_protocol.go index e9ea48eaf7..3d01fd3697 100644 --- a/action/protocol/mock_protocol.go +++ b/action/protocol/mock_protocol.go @@ -158,6 +158,45 @@ func (mr *MockStarterMockRecorder) Start(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockStarter)(nil).Start), arg0, arg1) } +// MockViewer is a mock of Viewer interface. +type MockViewer struct { + ctrl *gomock.Controller + recorder *MockViewerMockRecorder + isgomock struct{} +} + +// MockViewerMockRecorder is the mock recorder for MockViewer. +type MockViewerMockRecorder struct { + mock *MockViewer +} + +// NewMockViewer creates a new mock instance. +func NewMockViewer(ctrl *gomock.Controller) *MockViewer { + mock := &MockViewer{ctrl: ctrl} + mock.recorder = &MockViewerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockViewer) EXPECT() *MockViewerMockRecorder { + return m.recorder +} + +// ViewAt mocks base method. +func (m *MockViewer) ViewAt(arg0 context.Context, arg1 StateReader) (View, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewAt", arg0, arg1) + ret0, _ := ret[0].(View) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewAt indicates an expected call of ViewAt. +func (mr *MockViewerMockRecorder) ViewAt(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewAt", reflect.TypeOf((*MockViewer)(nil).ViewAt), arg0, arg1) +} + // MockGenesisStateCreator is a mock of GenesisStateCreator interface. type MockGenesisStateCreator struct { ctrl *gomock.Controller diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index 23991ee16f..72d2f693a5 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -241,6 +241,20 @@ func (mr *MockContractStakingIndexerMockRecorder) CreateEventProcessor(arg0, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEventProcessor", reflect.TypeOf((*MockContractStakingIndexer)(nil).CreateEventProcessor), arg0, arg1) } +// CreateMemoryEventHandler mocks base method. +func (m *MockContractStakingIndexer) CreateMemoryEventHandler(arg0 context.Context) EventHandler { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateMemoryEventHandler", arg0) + ret0, _ := ret[0].(EventHandler) + return ret0 +} + +// CreateMemoryEventHandler indicates an expected call of CreateMemoryEventHandler. +func (mr *MockContractStakingIndexerMockRecorder) CreateMemoryEventHandler(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMemoryEventHandler", reflect.TypeOf((*MockContractStakingIndexer)(nil).CreateMemoryEventHandler), arg0) +} + // Height mocks base method. func (m *MockContractStakingIndexer) Height() (uint64, error) { m.ctrl.T.Helper() @@ -398,6 +412,20 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) CreateEventProce return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEventProcessor", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).CreateEventProcessor), arg0, arg1) } +// CreateMemoryEventHandler mocks base method. +func (m *MockContractStakingIndexerWithBucketType) CreateMemoryEventHandler(arg0 context.Context) EventHandler { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateMemoryEventHandler", arg0) + ret0, _ := ret[0].(EventHandler) + return ret0 +} + +// CreateMemoryEventHandler indicates an expected call of CreateMemoryEventHandler. +func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) CreateMemoryEventHandler(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMemoryEventHandler", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).CreateMemoryEventHandler), arg0) +} + // Height mocks base method. func (m *MockContractStakingIndexerWithBucketType) Height() (uint64, error) { m.ctrl.T.Helper() diff --git a/action/protocol/staking/contractstaking/statemanager.go b/action/protocol/staking/contractstaking/statemanager.go index af68d0ee92..8f0647abaf 100644 --- a/action/protocol/staking/contractstaking/statemanager.go +++ b/action/protocol/staking/contractstaking/statemanager.go @@ -37,7 +37,7 @@ func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Add func (cs *ContractStakingStateManager) DeleteBucket(contractAddr address.Address, bucketID uint64) error { _, err := cs.sm.DelState( cs.makeOpts( - bucketTypeNamespaceOption(contractAddr), + contractNamespaceOption(contractAddr), bucketIDKeyOption(bucketID), protocol.ObjectOption(&Bucket{}), )..., diff --git a/action/protocol/staking/nfteventhandler_test.go b/action/protocol/staking/nfteventhandler_test.go new file mode 100644 index 0000000000..b31cd46d98 --- /dev/null +++ b/action/protocol/staking/nfteventhandler_test.go @@ -0,0 +1,144 @@ +package staking + +import ( + "math" + "math/big" + "testing" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/testutil/testdb" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func testCreateViewData(t *testing.T) *viewData { + testCandidates := CandidateList{ + { + Owner: identityset.Address(1), + Operator: identityset.Address(1), + Reward: identityset.Address(1), + Name: "cand1", + Votes: big.NewInt(1000), + SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, + SelfStake: big.NewInt(10), + }, + { + Owner: identityset.Address(2), + Operator: identityset.Address(2), + Reward: identityset.Address(2), + Name: "cand2", + Votes: big.NewInt(1000), + SelfStakeBucketIdx: candidateNoSelfStakeBucketIndex, + SelfStake: big.NewInt(0), + }, + } + candCenter, err := NewCandidateCenter(testCandidates) + require.NoError(t, err) + return &viewData{ + candCenter: candCenter, + bucketPool: &BucketPool{ + total: &totalAmount{ + amount: big.NewInt(100), + count: 1, + }, + }, + } +} + +// TestNewNFTBucketEventHandler tests the creation of a new NFT bucket event handler +func TestNewNFTBucketEventHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + sm := testdb.NewMockStateManager(ctrl) + sm.EXPECT().Height().Return(uint64(100), nil).AnyTimes() + sm.EXPECT().ReadView(gomock.Any()).Return(testCreateViewData(t), nil).AnyTimes() + handler, err := newNFTBucketEventHandler(sm, func(bkt *contractstaking.Bucket, height uint64) *big.Int { + return big.NewInt(100) + }) + require.NoError(t, err) + require.NotNil(t, handler) +} + +func TestNewNFTBucketEventHandlerSecondaryOnly(t *testing.T) { + ctrl := gomock.NewController(t) + sm := testdb.NewMockStateManager(ctrl) + sm.EXPECT().Height().Return(uint64(100), nil).AnyTimes() + sm.EXPECT().ReadView(gomock.Any()).Return(testCreateViewData(t), nil).AnyTimes() + handler, err := newNFTBucketEventHandlerSecondaryOnly(sm, func(bkt *contractstaking.Bucket, height uint64) *big.Int { + return big.NewInt(100) + }) + require.NoError(t, err) + require.NotNil(t, handler) +} + +func TestPutBucketType(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + sm := testdb.NewMockStateManager(ctrl) + sm.EXPECT().Height().Return(uint64(100), nil).AnyTimes() + sm.EXPECT().ReadView(gomock.Any()).Return(testCreateViewData(t), nil).AnyTimes() + handler, err := newNFTBucketEventHandler(sm, func(bkt *contractstaking.Bucket, height uint64) *big.Int { + return big.NewInt(100) + }) + require.NoError(t, err) + require.NotNil(t, handler) + iter, err := state.NewIterator([][]byte{}, [][]byte{}) + require.NoError(t, err) + sm.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(100), nil).Times(1) + sm.EXPECT().States(gomock.Any()).Return(uint64(100), iter, nil).Times(1) + contractAddr := identityset.Address(11) + require.NoError(t, handler.PutBucketType(contractAddr, &contractstaking.BucketType{ + Amount: big.NewInt(100), + Duration: 10, + ActivatedAt: 0, + })) + m, ok := handler.bucketTypes[contractAddr] + require.True(t, ok) + require.Equal(t, 1, len(m)) + bts, ok := handler.bucketTypesLookup[contractAddr] + require.True(t, ok) + require.Equal(t, uint64(0), bts[100][10]) +} + +func TestNFTEventHandlerBucket(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + sm := testdb.NewMockStateManager(ctrl) + sm.EXPECT().Height().Return(uint64(100), nil).AnyTimes() + require.NoError(t, sm.WriteView(_protocolID, testCreateViewData(t))) + handler, err := newNFTBucketEventHandler(sm, func(bkt *contractstaking.Bucket, height uint64) *big.Int { + return big.NewInt(100) + }) + require.NoError(t, err) + require.NotNil(t, handler) + + bucket := &contractstaking.Bucket{ + Candidate: identityset.Address(1), + Owner: identityset.Address(5), + StakedAmount: big.NewInt(100), + StakedDuration: 10, + CreatedAt: 1000, + UnlockedAt: 1010, + UnstakedAt: math.MaxUint64, + IsTimestampBased: false, + } + contractAddr := identityset.Address(11) + require.NoError(t, handler.PutBucket(contractAddr, 1, bucket)) + csm, err := NewCandidateStateManager(sm) + require.NoError(t, err) + candidate := csm.GetByIdentifier(identityset.Address(1)) + require.NotNil(t, candidate) + require.Equal(t, 0, big.NewInt(1100).Cmp(candidate.Votes)) + bucket, err = handler.DeductBucket(contractAddr, 1) + require.NoError(t, err) + require.NotNil(t, bucket) + candidate = csm.GetByIdentifier(identityset.Address(1)) + require.NotNil(t, candidate) + require.Equal(t, 0, big.NewInt(1000).Cmp(candidate.Votes)) + require.NoError(t, handler.DeleteBucket(contractAddr, 1)) +}