diff --git a/action/protocol/execution/evm/contract_adapter.go b/action/protocol/execution/evm/contract_adapter.go index a57bb6fb59..e7c5938dab 100644 --- a/action/protocol/execution/evm/contract_adapter.go +++ b/action/protocol/execution/evm/contract_adapter.go @@ -20,7 +20,7 @@ func newContractAdapter(addr hash.Hash160, account *state.Account, sm protocol.S if err != nil { return nil, errors.Wrap(err, "failed to create contract") } - v2, err := newContractErigon(addr, account, intra) + v2, err := newContractErigon(addr, account.Clone(), intra, sm) if err != nil { return nil, errors.Wrap(err, "failed to create contractV2") } diff --git a/action/protocol/execution/evm/contract_erigon.go b/action/protocol/execution/evm/contract_erigon.go index 25b1e2cd22..a9dd6c44bc 100644 --- a/action/protocol/execution/evm/contract_erigon.go +++ b/action/protocol/execution/evm/contract_erigon.go @@ -9,21 +9,25 @@ import ( "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/db/trie" + "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" ) type contractErigon struct { *state.Account intra *erigonstate.IntraBlockState + sr protocol.StateReader addr hash.Hash160 } -func newContractErigon(addr hash.Hash160, account *state.Account, intra *erigonstate.IntraBlockState) (Contract, error) { +func newContractErigon(addr hash.Hash160, account *state.Account, intra *erigonstate.IntraBlockState, sr protocol.StateReader) (Contract, error) { c := &contractErigon{ Account: account, intra: intra, addr: addr, + sr: sr, } return c, nil } @@ -32,14 +36,16 @@ func (c *contractErigon) GetCommittedState(key hash.Hash256) ([]byte, error) { k := libcommon.Hash(key) v := uint256.NewInt(0) c.intra.GetCommittedState(libcommon.Address(c.addr), &k, v) - return v.Bytes(), nil + h := hash.BytesToHash256(v.Bytes()) + return h[:], nil } func (c *contractErigon) GetState(key hash.Hash256) ([]byte, error) { k := libcommon.Hash(key) v := uint256.NewInt(0) c.intra.GetState(libcommon.Address(c.addr), &k, v) - return v.Bytes(), nil + h := hash.BytesToHash256(v.Bytes()) + return h[:], nil } func (c *contractErigon) SetState(key hash.Hash256, value []byte) error { @@ -58,10 +64,10 @@ func (c *contractErigon) SetCode(hash hash.Hash256, code []byte) { func (c *contractErigon) SelfState() *state.Account { acc := &state.Account{} - acc.SetPendingNonce(c.intra.GetNonce(libcommon.Address(c.addr))) - acc.AddBalance(c.intra.GetBalance(libcommon.Address(c.addr)).ToBig()) - codeHash := c.intra.GetCodeHash(libcommon.Address(c.addr)) - acc.CodeHash = codeHash[:] + _, err := c.sr.State(acc, protocol.LegacyKeyOption(c.addr), protocol.ErigonStoreOnlyOption()) + if err != nil { + log.S().Panicf("failed to load account %x: %v", c.addr, err) + } return acc } diff --git a/action/protocol/execution/evm/evmstatedbadapter_erigon.go b/action/protocol/execution/evm/evmstatedbadapter_erigon.go index 2a5b48e994..abe82495de 100644 --- a/action/protocol/execution/evm/evmstatedbadapter_erigon.go +++ b/action/protocol/execution/evm/evmstatedbadapter_erigon.go @@ -53,7 +53,7 @@ func NewErigonStateDBAdapterDryrun(adapter *StateDBAdapter, ) *ErigonStateDBAdapterDryrun { a := NewErigonStateDBAdapter(adapter, intra) adapter.newContract = func(addr hash.Hash160, account *state.Account) (Contract, error) { - return newContractErigon(addr, account, intra) + return newContractErigon(addr, account, intra, adapter.sm) } return &ErigonStateDBAdapterDryrun{ ErigonStateDBAdapter: a, diff --git a/action/protocol/rewarding/fund.go b/action/protocol/rewarding/fund.go index 2131d447fe..5e319b33c0 100644 --- a/action/protocol/rewarding/fund.go +++ b/action/protocol/rewarding/fund.go @@ -23,6 +23,8 @@ import ( "github.com/iotexproject/iotex-core/v2/systemcontracts" ) +type Fund = fund + // fund stores the balance of the rewarding fund. The difference between total and available balance should be // equal to the unclaimed balance in all reward accounts type fund struct { @@ -59,17 +61,52 @@ func (f *fund) Deserialize(data []byte) error { } func (f *fund) Encode() (systemcontracts.GenericValue, error) { - data, err := f.Serialize() + d1, err := proto.Marshal(&rewardingpb.Fund{ + TotalBalance: f.totalBalance.String(), + }) + if err != nil { + return systemcontracts.GenericValue{}, err + } + d2, err := proto.Marshal(&rewardingpb.Fund{ + UnclaimedBalance: f.unclaimedBalance.String(), + }) if err != nil { return systemcontracts.GenericValue{}, err } return systemcontracts.GenericValue{ - AuxiliaryData: data, + PrimaryData: d1, + SecondaryData: d2, }, nil } func (f *fund) Decode(v systemcontracts.GenericValue) error { - return f.Deserialize(v.AuxiliaryData) + gen1 := rewardingpb.Fund{} + if err := proto.Unmarshal(v.PrimaryData, &gen1); err != nil { + return err + } + var totalBalance = big.NewInt(0) + if len(gen1.TotalBalance) > 0 { + b, ok := new(big.Int).SetString(gen1.TotalBalance, 10) + if !ok { + return errors.Errorf("failed to set total balance from string: %s", gen1.TotalBalance) + } + totalBalance = b + } + gen2 := rewardingpb.Fund{} + if err := proto.Unmarshal(v.SecondaryData, &gen2); err != nil { + return err + } + var unclaimedBalance = big.NewInt(0) + if len(gen2.UnclaimedBalance) > 0 { + b, ok := new(big.Int).SetString(gen2.UnclaimedBalance, 10) + if !ok { + return errors.Errorf("failed to set unclaimed balance from string: %s", gen2.UnclaimedBalance) + } + unclaimedBalance = b + } + f.totalBalance = totalBalance + f.unclaimedBalance = unclaimedBalance + return nil } // Deposit deposits token into the rewarding fund diff --git a/action/protocol/rewarding/protocol.go b/action/protocol/rewarding/protocol.go index 1c7cf02055..813c84b53d 100644 --- a/action/protocol/rewarding/protocol.go +++ b/action/protocol/rewarding/protocol.go @@ -21,6 +21,7 @@ import ( 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/enc" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" ) @@ -102,6 +103,13 @@ func FindProtocol(registry *protocol.Registry) *Protocol { func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager) error { g := genesis.MustExtractGenesisContext(ctx) blkCtx := protocol.MustGetBlockCtx(ctx) + // set current block reward not granted for erigon db + var indexBytes [8]byte + enc.MachineEndian.PutUint64(indexBytes[:], blkCtx.BlockHeight) + err := p.deleteState(ctx, sm, append(_blockRewardHistoryKeyPrefix, indexBytes[:]...), &rewardHistory{}, protocol.ErigonStoreOnlyOption()) + if err != nil && !errors.Is(err, state.ErrErigonStoreNotSupported) { + return err + } switch blkCtx.BlockHeight { case g.AleutianBlockHeight: return p.SetReward(ctx, sm, g.AleutianEpochReward(), false) @@ -348,10 +356,11 @@ func (p *Protocol) putState(ctx context.Context, sm protocol.StateManager, key [ return p.putStateV1(sm, key, value) } -func (p *Protocol) putStateV1(sm protocol.StateManager, key []byte, value interface{}) error { +func (p *Protocol) putStateV1(sm protocol.StateManager, key []byte, value interface{}, opts ...protocol.StateOption) error { orgKey := append(p.keyPrefix, key...) keyHash := hash.Hash160b(orgKey) - _, err := sm.PutState(value, protocol.LegacyKeyOption(keyHash), protocol.ErigonStoreKeyOption(orgKey)) + opts = append(opts, protocol.LegacyKeyOption(keyHash), protocol.ErigonStoreKeyOption(orgKey)) + _, err := sm.PutState(value, opts...) return err } @@ -361,10 +370,29 @@ func (p *Protocol) putStateV2(sm protocol.StateManager, key []byte, value interf return err } -func (p *Protocol) deleteStateV1(sm protocol.StateManager, key []byte, obj any) error { +func (p *Protocol) deleteState(ctx context.Context, sm protocol.StateManager, key []byte, obj any, opts ...protocol.StateOption) error { + if useV2Storage(ctx) { + return p.deleteStateV2(sm, key, obj, opts...) + } + return p.deleteStateV1(sm, key, obj, opts...) +} + +func (p *Protocol) deleteStateV1(sm protocol.StateManager, key []byte, obj any, opts ...protocol.StateOption) error { orgKey := append(p.keyPrefix, key...) keyHash := hash.Hash160b(orgKey) - _, err := sm.DelState(protocol.LegacyKeyOption(keyHash), protocol.ObjectOption(obj), protocol.ErigonStoreKeyOption(orgKey)) + opt := append(opts, protocol.LegacyKeyOption(keyHash), protocol.ObjectOption(obj), protocol.ErigonStoreKeyOption(orgKey)) + _, err := sm.DelState(opt...) + 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, value any, opts ...protocol.StateOption) error { + k := append(p.keyPrefix, key...) + opt := append(opts, protocol.KeyOption(k), protocol.ObjectOption(value), protocol.NamespaceOption(_v2RewardingNamespace)) + _, err := sm.DelState(opt...) 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 574bee10b7..fdb3c53073 100644 --- a/action/protocol/rewarding/reward.go +++ b/action/protocol/rewarding/reward.go @@ -10,6 +10,7 @@ import ( "math/big" "github.com/pkg/errors" + "go.uber.org/zap" "google.golang.org/protobuf/proto" "github.com/iotexproject/go-pkgs/hash" @@ -29,6 +30,8 @@ import ( "github.com/iotexproject/iotex-core/v2/systemcontracts" ) +type RewardHistory = rewardHistory + // rewardHistory is the dummy struct to record a reward. Only key matters. type rewardHistory struct{} @@ -114,6 +117,24 @@ func (p *Protocol) GrantBlockReward( ) (*action.Log, error) { actionCtx := protocol.MustGetActionCtx(ctx) blkCtx := protocol.MustGetBlockCtx(ctx) + fCtx := protocol.MustGetFeatureCtx(ctx) + + if fCtx.UseV2Storage { + var indexBytes [8]byte + enc.MachineEndian.PutUint64(indexBytes[:], blkCtx.BlockHeight) + key := append(_blockRewardHistoryKeyPrefix, indexBytes[:]...) + err := p.deleteStateV1(sm, key, &rewardHistory{}, protocol.ErigonStoreOnlyOption()) + if err != nil && !errors.Is(err, state.ErrErigonStoreNotSupported) { + return nil, err + } + defer func() { + err = p.putStateV1(sm, key, &rewardHistory{}, protocol.ErigonStoreOnlyOption()) + if err != nil && !errors.Is(err, state.ErrErigonStoreNotSupported) { + log.L().Panic("failed to put block reward history in Erigon store", zap.Error(err)) + } + }() + } + if err := p.assertNoRewardYet(ctx, sm, _blockRewardHistoryKeyPrefix, blkCtx.BlockHeight); err != nil { return nil, err } diff --git a/action/protocol/staking/candidate.go b/action/protocol/staking/candidate.go index 19d600f2f4..f8a10879d0 100644 --- a/action/protocol/staking/candidate.go +++ b/action/protocol/staking/candidate.go @@ -18,7 +18,6 @@ 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/util/byteutil" "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontracts" ) @@ -418,7 +417,7 @@ func (l *CandidateList) Deserialize(buf []byte) error { } // Encode encodes candidate list into generic value -func (l *CandidateList) Encode() ([][]byte, []systemcontracts.GenericValue, error) { +func (l *CandidateList) Encodes() ([][]byte, []systemcontracts.GenericValue, error) { var ( keys [][]byte values []systemcontracts.GenericValue @@ -429,7 +428,11 @@ func (l *CandidateList) Encode() ([][]byte, []systemcontracts.GenericValue, erro if err != nil { return nil, nil, errors.Wrap(err, "failed to encode candidate") } - gv.AuxiliaryData = byteutil.Uint64ToBytes(uint64(idx)) + var nextAddr []byte + if idx < len(*l)-1 { + nextAddr = (*l)[idx+1].GetIdentifier().Bytes() + } + gv.AuxiliaryData = nextAddr keys = append(keys, key) values = append(values, gv) } @@ -437,21 +440,32 @@ func (l *CandidateList) Encode() ([][]byte, []systemcontracts.GenericValue, erro } // Decode decodes candidate list from generic value -func (l *CandidateList) Decode(keys [][]byte, gvs []systemcontracts.GenericValue) error { - // reconstruct candidate list - // the order of keys and gvs are guaranteed to be the same - candidateMap := make(map[uint64]*Candidate) - for _, gv := range gvs { +func (l *CandidateList) Decodes(keys [][]byte, gvs []systemcontracts.GenericValue) error { + if len(keys) != len(gvs) { + return errors.New("mismatched keys and generic values length") + } + if len(keys) == 0 { + *l = CandidateList{} + return nil + } + + candidates, err := state.DecodeOrderedKvList(keys, gvs, func(k []byte, v systemcontracts.GenericValue) (*Candidate, string, string, error) { c := &Candidate{} - if err := c.Decode(gv); err != nil { - return errors.Wrap(err, "failed to decode candidate") + if err := c.Decode(v); err != nil { + return nil, "", "", errors.Wrap(err, "failed to decode candidate") } - idx := byteutil.BytesToUint64(gv.AuxiliaryData) - candidateMap[idx] = c - } - candidates := make(CandidateList, 0, len(candidateMap)) - for i := 0; i < len(candidateMap); i++ { - candidates = append(candidates, candidateMap[uint64(i)]) + var next string + if len(v.AuxiliaryData) > 0 { + nextAddr, err := address.FromBytes(v.AuxiliaryData) + if err != nil { + return nil, "", "", errors.Wrap(err, "failed to get next candidate address") + } + next = nextAddr.String() + } + return c, c.GetIdentifier().String(), next, nil + }) + if err != nil { + return errors.Wrap(err, "failed to decode candidate list") } *l = candidates return nil diff --git a/state/candidate.go b/state/candidate.go index 8ba4fa1da8..d54029c401 100644 --- a/state/candidate.go +++ b/state/candidate.go @@ -15,7 +15,6 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/proto" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" "github.com/iotexproject/iotex-core/v2/systemcontracts" "github.com/iotexproject/iotex-address/address" @@ -154,13 +153,38 @@ func (l *CandidateList) LoadProto(candList *iotextypes.CandidateList) error { } // Encode encodes a CandidateList into a GenericValue -func (l *CandidateList) Encode() ([][]byte, []systemcontracts.GenericValue, error) { +func (l *CandidateList) Encode() (systemcontracts.GenericValue, error) { + data, err := l.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, errors.Wrap(err, "failed to serialize candidate list") + } + return systemcontracts.GenericValue{ + PrimaryData: data, + }, nil +} + +// Decode decodes a GenericValue into CandidateList +func (l *CandidateList) Decode(gv systemcontracts.GenericValue) error { + return l.Deserialize(gv.PrimaryData) +} + +// Encodes encodes a CandidateList into a GenericValue +func (l *CandidateList) Encodes() ([][]byte, []systemcontracts.GenericValue, error) { var ( suffix [][]byte values []systemcontracts.GenericValue ) for idx, cand := range *l { - data, err := cand.Serialize() + pbCand := candidateToPb(cand) + dataVotes, err := proto.Marshal(&iotextypes.Candidate{ + Votes: pbCand.Votes, + }) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to serialize candidate votes") + } + pbCand.Address = "" // address is stored in the suffix + pbCand.Votes = nil // votes is stored in the secondary data + data, err := proto.Marshal(pbCand) if err != nil { return nil, nil, errors.Wrap(err, "failed to serialize candidate") } @@ -168,29 +192,71 @@ func (l *CandidateList) Encode() ([][]byte, []systemcontracts.GenericValue, erro if err != nil { return nil, nil, errors.Wrapf(err, "failed to get the hash of the address %s", cand.Address) } + // Use linked list format: AuxiliaryData stores the next node's address + // For the last element, AuxiliaryData is nil + var nextAddr []byte + if idx < len(*l)-1 { + nextAddrObj, err := address.FromString((*l)[idx+1].Address) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to get the address of the next candidate %s", (*l)[idx+1].Address) + } + nextAddr = nextAddrObj.Bytes() + } suffix = append(suffix, addr.Bytes()) - values = append(values, systemcontracts.GenericValue{PrimaryData: data, SecondaryData: byteutil.Uint64ToBytes(uint64(idx))}) + values = append(values, systemcontracts.GenericValue{ + PrimaryData: data, + SecondaryData: dataVotes, + AuxiliaryData: nextAddr}) } return suffix, values, nil } -// Decode decodes a GenericValue into CandidateList -func (l *CandidateList) Decode(suffixs [][]byte, values []systemcontracts.GenericValue) error { - // reconstruct candidate list from values - // the order of candidates in the list is determined by the SecondaryData of GenericValue - candidateMap := make(map[uint64]*Candidate) - for _, gv := range values { - cand := &Candidate{} - if err := cand.Deserialize(gv.PrimaryData); err != nil { - return errors.Wrap(err, "failed to deserialize candidate") +// Decodes decodes a GenericValue into CandidateList +func (l *CandidateList) Decodes(suffixs [][]byte, values []systemcontracts.GenericValue) error { + if len(suffixs) != len(values) { + return errors.New("suffix and values length mismatch") + } + if len(suffixs) == 0 { + *l = CandidateList{} + return nil + } + + decoder := func(k []byte, v systemcontracts.GenericValue) (*Candidate, string, string, error) { + pb := &iotextypes.Candidate{} + if err := proto.Unmarshal(v.PrimaryData, pb); err != nil { + return nil, "", "", errors.Wrap(err, "failed to unmarshal candidate") + } + addr, err := address.FromBytes(k) + if err != nil { + return nil, "", "", errors.Wrapf(err, "failed to get the string of the address from bytes %x", k) + } + pb.Address = addr.String() + // Load votes from SecondaryData + pbVotes := &iotextypes.Candidate{} + if err := proto.Unmarshal(v.SecondaryData, pbVotes); err != nil { + return nil, "", "", errors.Wrap(err, "failed to unmarshal candidate votes") } - index := byteutil.BytesToUint64(gv.SecondaryData) - candidateMap[index] = cand + pb.Votes = pbVotes.Votes + // Convert pb to candidate + cand, err := pbToCandidate(pb) + if err != nil { + return nil, "", "", errors.Wrap(err, "failed to convert protobuf's candidate message to candidate") + } + var next string + if len(v.AuxiliaryData) > 0 { + nextAddr, err := address.FromBytes(v.AuxiliaryData) + if err != nil { + return nil, "", "", errors.Wrapf(err, "failed to get the string of the next address from bytes %x", v.AuxiliaryData) + } + next = nextAddr.String() + } + return cand, addr.String(), next, nil } - candidates := make(CandidateList, 0, len(candidateMap)) - for i := 0; i < len(candidateMap); i++ { - candidates = append(candidates, candidateMap[uint64(i)]) + candidates, err := DecodeOrderedKvList(suffixs, values, decoder) + if err != nil { + return err } + *l = candidates return nil } diff --git a/state/candidate_linklist.go b/state/candidate_linklist.go new file mode 100644 index 0000000000..aec672fb2b --- /dev/null +++ b/state/candidate_linklist.go @@ -0,0 +1,107 @@ +package state + +import ( + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type linkedListNode[T any] struct { + data T + nextAddr string +} + +// DecodeOrderedKvList decodes a list of key-value pairs representing a linked list into an ordered slice +func DecodeOrderedKvList[T any](keys [][]byte, values []systemcontracts.GenericValue, decoder func(k []byte, v systemcontracts.GenericValue) (T, string, string, error)) ([]T, error) { + nodeMap, err := buildCandidateMap(keys, values, decoder) + if err != nil { + return nil, errors.Wrap(err, "failed to build candidate map") + } + + headAddr, err := findLinkedListHead(nodeMap) + if err != nil { + return nil, errors.Wrap(err, "failed to find head of candidate list") + } + + result, err := traverseLinkedList(headAddr, nodeMap) + if err != nil { + return nil, errors.Wrap(err, "failed to traverse candidate linked list") + } + + return result, nil +} + +// buildCandidateMap builds a generic map from address bytes to candidate nodes +func buildCandidateMap[T any]( + keys [][]byte, + values []systemcontracts.GenericValue, + decoder func(k []byte, v systemcontracts.GenericValue) (T, string, string, error), +) (map[string]*linkedListNode[T], error) { + nodeMap := make(map[string]*linkedListNode[T], len(keys)) + + for kid, gv := range values { + data, id, next, err := decoder(keys[kid], gv) + if err != nil { + return nil, errors.Wrap(err, "failed to decode candidate data") + } + nodeMap[id] = &linkedListNode[T]{ + data: data, + nextAddr: next, + } + } + + return nodeMap, nil +} + +// findLinkedListHead finds the head of the linked list (not pointed to by any node) +func findLinkedListHead[T any](nodeMap map[string]*linkedListNode[T]) (string, error) { + // Mark all addresses that are pointed to + pointedTo := make(map[string]bool, len(nodeMap)) + for _, node := range nodeMap { + if len(node.nextAddr) > 0 { + pointedTo[node.nextAddr] = true + } + } + + // Find the address that is not pointed to by any node + for addrKey := range nodeMap { + if !pointedTo[addrKey] { + return addrKey, nil + } + } + + return "", errors.New("failed to find head of candidate list") +} + +// traverseLinkedList traverses the linked list and returns an ordered list +func traverseLinkedList[T any](headAddr string, nodeMap map[string]*linkedListNode[T]) ([]T, error) { + result := make([]T, 0, len(nodeMap)) + visited := make(map[string]bool, len(nodeMap)) + currentAddr := headAddr + + for currentAddr != "" { + addrKey := currentAddr + + // Check for circular reference + if visited[addrKey] { + return nil, errors.New("circular reference detected in candidate list") + } + visited[addrKey] = true + + // Get current node + node, exists := nodeMap[addrKey] + if !exists { + return nil, errors.Errorf("missing candidate for address %x in linked list", currentAddr) + } + + result = append(result, node.data) + currentAddr = node.nextAddr + } + + // Verify all nodes were traversed + if len(result) != len(nodeMap) { + return nil, errors.Errorf("incomplete traversal: %d/%d candidates", len(result), len(nodeMap)) + } + + return result, nil +} diff --git a/state/factory/erigonstore/accountstorage.go b/state/factory/erigonstore/accountstorage.go index 594b210208..8a41b19873 100644 --- a/state/factory/erigonstore/accountstorage.go +++ b/state/factory/erigonstore/accountstorage.go @@ -2,7 +2,6 @@ package erigonstore import ( erigonComm "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon/core/types/accounts" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" "github.com/pkg/errors" @@ -81,14 +80,18 @@ func (as *accountStorage) Load(key []byte, obj any) error { case accountpb.AccountType_ZERO_NONCE: pbAcc.Nonce = nonce case accountpb.AccountType_DEFAULT: - pbAcc.Nonce = nonce - 1 + if nonce == 0 { + pbAcc.Nonce = nonce + } else { + pbAcc.Nonce = nonce - 1 + } default: return errors.Errorf("unknown account type %v for address %x", pbAcc.Type, addr.Bytes()) } - - if ch := as.backend.intraBlockState.GetCodeHash(addr); !accounts.IsEmptyCodeHash(ch) { - pbAcc.CodeHash = ch.Bytes() - } + // if ch := as.backend.intraBlockState.GetCodeHash(addr); !accounts.IsEmptyCodeHash(ch) { + // pbAcc.CodeHash = ch.Bytes() + // } + pbAcc.CodeHash = as.backend.intraBlockState.GetCodeHash(addr).Bytes() acct.FromProto(pbAcc) return nil } diff --git a/state/factory/erigonstore/iterator.go b/state/factory/erigonstore/iterator.go index c89498e554..ad83b7ac07 100644 --- a/state/factory/erigonstore/iterator.go +++ b/state/factory/erigonstore/iterator.go @@ -35,11 +35,11 @@ func (gvoi *GenericValueObjectIterator) Next(o interface{}) ([]byte, error) { } value := gvoi.values[gvoi.cur] key := gvoi.keys[gvoi.cur] - gvoi.cur++ if gvoi.exists != nil && !gvoi.exists[gvoi.cur] { gvoi.cur++ return key, state.ErrNilValue } + gvoi.cur++ if err := systemcontracts.DecodeGenericValue(o, value); err != nil { return nil, err } diff --git a/state/factory/erigonstore/keysplitstorage.go b/state/factory/erigonstore/keysplitstorage.go index b093eeed61..7cc0bca4c4 100644 --- a/state/factory/erigonstore/keysplitstorage.go +++ b/state/factory/erigonstore/keysplitstorage.go @@ -1,9 +1,10 @@ package erigonstore import ( + "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontracts" - "github.com/pkg/errors" ) type KeySplitter func(key []byte) (part1 []byte, part2 []byte) @@ -14,27 +15,30 @@ type keySplitContainer interface { } type keySplitContractStorage struct { - contract systemcontracts.StorageContract - keySplit KeySplitter - fallback ObjectStorage + contract systemcontracts.StorageContract + keySplit KeySplitter + keyPrefixStorage map[string]ObjectStorage + fallback ObjectStorage } -func newKeySplitContractStorageWithfallback(contract systemcontracts.StorageContract, split KeySplitter, fallback ObjectStorage) *keySplitContractStorage { +func newKeySplitContractStorageWithfallback(contract systemcontracts.StorageContract, split KeySplitter, fallback ObjectStorage, keyPrefixStorage map[string]ObjectStorage) *keySplitContractStorage { return &keySplitContractStorage{ - contract: contract, - keySplit: split, - fallback: fallback, + contract: contract, + keySplit: split, + fallback: fallback, + keyPrefixStorage: keyPrefixStorage, } } func (rhs *keySplitContractStorage) Store(key []byte, obj any) error { pf, sf := rhs.keySplit(key) + fallback := rhs.matchStorage(key) if len(sf) == 0 { - return rhs.fallback.Store(key, obj) + return fallback.Store(key, obj) } gvc, ok := obj.(keySplitContainer) if !ok { - return rhs.fallback.Store(pf, obj) + return fallback.Store(pf, obj) } value, err := gvc.Encode(sf) if err != nil { @@ -45,12 +49,13 @@ func (rhs *keySplitContractStorage) Store(key []byte, obj any) error { func (rhs *keySplitContractStorage) Load(key []byte, obj any) error { pf, sf := rhs.keySplit(key) + fallback := rhs.matchStorage(key) if len(sf) == 0 { - return rhs.fallback.Load(key, obj) + return fallback.Load(key, obj) } gvc, ok := obj.(keySplitContainer) if !ok { - return rhs.fallback.Load(pf, obj) + return fallback.Load(pf, obj) } value, err := rhs.contract.Get(pf) if err != nil { @@ -64,10 +69,11 @@ func (rhs *keySplitContractStorage) Load(key []byte, obj any) error { func (rhs *keySplitContractStorage) Delete(key []byte) error { pf, sf := rhs.keySplit(key) + fallback := rhs.matchStorage(key) if len(sf) == 0 { - return rhs.fallback.Delete(key) + return fallback.Delete(key) } - return rhs.fallback.Delete(pf) + return fallback.Delete(pf) } func (rhs *keySplitContractStorage) List() (state.Iterator, error) { @@ -77,3 +83,13 @@ func (rhs *keySplitContractStorage) List() (state.Iterator, error) { func (rhs *keySplitContractStorage) Batch(keys [][]byte) (state.Iterator, error) { return nil, errors.New("not implemented") } + +func (rhs *keySplitContractStorage) matchStorage(key []byte) ObjectStorage { + for prefix, os := range rhs.keyPrefixStorage { + sk := string(key) + if len(sk) >= len(prefix) && sk[:len(prefix)] == prefix { + return os + } + } + return rhs.fallback +} diff --git a/state/factory/erigonstore/kvliststorage.go b/state/factory/erigonstore/kvliststorage.go index 358a6e62e6..3830a6a667 100644 --- a/state/factory/erigonstore/kvliststorage.go +++ b/state/factory/erigonstore/kvliststorage.go @@ -3,14 +3,15 @@ package erigonstore import ( "bytes" + "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/state" "github.com/iotexproject/iotex-core/v2/systemcontracts" - "github.com/pkg/errors" ) type kvListContainer interface { - Encode() ([][]byte, []systemcontracts.GenericValue, error) - Decode(keys [][]byte, values []systemcontracts.GenericValue) error + Encodes() ([][]byte, []systemcontracts.GenericValue, error) + Decodes(keys [][]byte, values []systemcontracts.GenericValue) error } type kvListStorage struct { @@ -28,7 +29,15 @@ func (cos *kvListStorage) Store(prefix []byte, obj any) error { if !ok { return errors.Errorf("object of type %T does not supported", obj) } - keys, values, err := ct.Encode() + cnt, err := cos.contract.Count() + if err != nil { + return err + } + retval, err := cos.contract.ListKeys(0, cnt.Uint64()) + if err != nil { + return err + } + keys, values, err := ct.Encodes() if err != nil { return err } @@ -41,14 +50,6 @@ func (cos *kvListStorage) Store(prefix []byte, obj any) error { newKeys[string(nk)] = struct{}{} } // remove keys not in the new list - cnt, err := cos.contract.Count() - if err != nil { - return err - } - retval, err := cos.contract.ListKeys(0, cnt.Uint64()) - if err != nil { - return err - } for _, k := range retval.KeyList { if len(k) >= len(prefix) && bytes.Equal(k[:len(prefix)], prefix) { if _, exists := newKeys[string(k)]; !exists { @@ -85,7 +86,10 @@ func (cos *kvListStorage) Load(prefix []byte, obj any) error { values = append(values, retval.Values[i]) } } - return ct.Decode(keys, values) + if len(keys) == 0 { + return errors.Wrapf(state.ErrStateNotExist, "prefix: %x", prefix) + } + return ct.Decodes(keys, values) } func (cos *kvListStorage) Delete(prefix []byte) error { diff --git a/state/factory/erigonstore/registry.go b/state/factory/erigonstore/registry.go index e66b27cff1..bde277ff41 100644 --- a/state/factory/erigonstore/registry.go +++ b/state/factory/erigonstore/registry.go @@ -29,11 +29,17 @@ var ( // ObjectStorageRegistry is a registry for object storage type ObjectStorageRegistry struct { - contracts map[string]map[reflect.Type]int - ns map[string]int - nsPrefix map[string]int - spliter map[int]KeySplitter - kvList map[int]struct{} + contracts map[string]map[reflect.Type]int + ns map[string]int + nsPrefix map[string]int + spliter map[int]KeySplitter + kvList map[int]struct{} + rewardingHistoryPrefix map[int]rewardingHistoryConfig +} + +type rewardingHistoryConfig struct { + Prefixs [][]byte + KeySplit KeySplitter } type RegisterOption func(int, *ObjectStorageRegistry) @@ -50,14 +56,23 @@ func WithKVListOption() RegisterOption { } } +func WithRewardingHistoryPrefixOption(split KeySplitter, prefix ...[]byte) RegisterOption { + return func(index int, osr *ObjectStorageRegistry) { + osr.rewardingHistoryPrefix[index] = rewardingHistoryConfig{ + Prefixs: prefix, + KeySplit: split, + } + } +} + func init() { rewardHistoryPrefixs := [][]byte{ append(state.RewardingKeyPrefix[:], state.BlockRewardHistoryKeyPrefix...), append(state.RewardingKeyPrefix[:], state.EpochRewardHistoryKeyPrefix...), } - pollPrefix := [][]byte{ - []byte(state.PollCandidatesPrefix), - } + // pollPrefix := [][]byte{ + // []byte(state.PollCandidatesPrefix), + // } genKeySplit := func(prefixs [][]byte) KeySplitter { return func(key []byte) (part1 []byte, part2 []byte) { for _, p := range prefixs { @@ -69,11 +84,12 @@ func init() { return key, nil } } - rewardKeySplit := genKeySplit(rewardHistoryPrefixs) - pollKeySplit := genKeySplit(pollPrefix) + epochRewardKeySplit := genKeySplit(rewardHistoryPrefixs[1:]) + blockRewardKeySplit := genKeySplit(rewardHistoryPrefixs[:1]) + // pollKeySplit := genKeySplit(pollPrefix) - assertions.MustNoError(storageRegistry.RegisterNamespace(state.AccountKVNamespace, RewardingContractV1Index, WithKeySplitOption(rewardKeySplit))) - assertions.MustNoError(storageRegistry.RegisterNamespace(state.RewardingNamespace, RewardingContractV2Index, WithKeySplitOption(rewardKeySplit))) + assertions.MustNoError(storageRegistry.RegisterNamespace(state.AccountKVNamespace, RewardingContractV1Index, WithKeySplitOption(epochRewardKeySplit), WithRewardingHistoryPrefixOption(blockRewardKeySplit, rewardHistoryPrefixs[0]))) + assertions.MustNoError(storageRegistry.RegisterNamespace(state.RewardingNamespace, RewardingContractV2Index, WithKeySplitOption(epochRewardKeySplit), WithRewardingHistoryPrefixOption(blockRewardKeySplit, rewardHistoryPrefixs[0]))) assertions.MustNoError(storageRegistry.RegisterNamespace(state.CandidateNamespace, CandidatesContractIndex)) assertions.MustNoError(storageRegistry.RegisterNamespace(state.CandsMapNamespace, CandidateMapContractIndex, WithKVListOption())) assertions.MustNoError(storageRegistry.RegisterNamespace(state.StakingNamespace, BucketPoolContractIndex)) @@ -83,7 +99,7 @@ func init() { assertions.MustNoError(storageRegistry.RegisterNamespacePrefix(state.ContractStakingBucketTypeNamespacePrefix, ContractStakingBucketContractIndex)) assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.AccountKVNamespace, &state.Account{}, AccountIndex)) - assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.AccountKVNamespace, &state.CandidateList{}, PollLegacyCandidateListContractIndex, WithKeySplitOption(pollKeySplit), WithKVListOption())) + assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.AccountKVNamespace, &state.CandidateList{}, PollLegacyCandidateListContractIndex)) assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.SystemNamespace, &state.CandidateList{}, PollCandidateListContractIndex, WithKVListOption())) assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.SystemNamespace, &vote.UnproductiveDelegate{}, PollUnproductiveDelegateContractIndex)) assertions.MustNoError(storageRegistry.RegisterObjectStorage(state.SystemNamespace, &vote.ProbationList{}, PollProbationListContractIndex)) @@ -100,11 +116,12 @@ func GetObjectStorageRegistry() *ObjectStorageRegistry { func newObjectStorageRegistry() *ObjectStorageRegistry { return &ObjectStorageRegistry{ - contracts: make(map[string]map[reflect.Type]int), - ns: make(map[string]int), - nsPrefix: make(map[string]int), - spliter: make(map[int]KeySplitter), - kvList: make(map[int]struct{}), + contracts: make(map[string]map[reflect.Type]int), + ns: make(map[string]int), + nsPrefix: make(map[string]int), + spliter: make(map[int]KeySplitter), + kvList: make(map[int]struct{}), + rewardingHistoryPrefix: make(map[int]rewardingHistoryConfig), } } @@ -135,7 +152,13 @@ func (osr *ObjectStorageRegistry) ObjectStorage(ns string, obj any, backend *con os = newKVListStorage(contract) } if split != nil { - os = newKeySplitContractStorageWithfallback(contract, split, os) + prefixFallbacks := map[string]ObjectStorage{} + if config, ok := osr.rewardingHistoryPrefix[contractIndex]; ok { + for _, prefix := range config.Prefixs { + prefixFallbacks[string(prefix)] = newRewardHistoryStorage(contract, backend.height, config.KeySplit) + } + } + os = newKeySplitContractStorageWithfallback(contract, split, os, prefixFallbacks) } return os, nil default: @@ -152,7 +175,13 @@ func (osr *ObjectStorageRegistry) ObjectStorage(ns string, obj any, backend *con os = newKVListStorage(contract) } if split != nil { - os = newKeySplitContractStorageWithfallback(contract, split, os) + prefixFallbacks := map[string]ObjectStorage{} + if config, ok := osr.rewardingHistoryPrefix[contractIndex]; ok { + for _, prefix := range config.Prefixs { + prefixFallbacks[string(prefix)] = newRewardHistoryStorage(contract, backend.height, config.KeySplit) + } + } + os = newKeySplitContractStorageWithfallback(contract, split, os, prefixFallbacks) } return os, nil } diff --git a/state/factory/erigonstore/rewardhistorystorage.go b/state/factory/erigonstore/rewardhistorystorage.go new file mode 100644 index 0000000000..c583cd6f81 --- /dev/null +++ b/state/factory/erigonstore/rewardhistorystorage.go @@ -0,0 +1,56 @@ +package erigonstore + +import ( + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/pkg/enc" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type rewardHistoryStorage struct { + contract systemcontracts.StorageContract + height uint64 + keySplit KeySplitter +} + +func newRewardHistoryStorage(contract systemcontracts.StorageContract, height uint64, keySplit KeySplitter) *rewardHistoryStorage { + return &rewardHistoryStorage{ + contract: contract, + height: height, + keySplit: keySplit, + } +} + +func (ds *rewardHistoryStorage) Store(key []byte, obj any) error { + _, err := ds.contract.Remove(key) + return err +} + +func (ds *rewardHistoryStorage) Delete(key []byte) error { + return ds.contract.Put(key, systemcontracts.GenericValue{}) +} + +func (ds *rewardHistoryStorage) Load(key []byte, obj any) error { + _, heightKey := ds.keySplit(key) + height := enc.MachineEndian.Uint64(heightKey) + if height > ds.height { + return errors.Wrapf(state.ErrStateNotExist, "key: %x at height %d", key, height) + } + result, err := ds.contract.Get(key) + if err != nil { + return errors.Wrapf(err, "failed to get data for key %x", key) + } + if result.KeyExists { + return errors.Wrapf(state.ErrStateNotExist, "key: %x", key) + } + return nil +} + +func (ds *rewardHistoryStorage) Batch(keys [][]byte) (state.Iterator, error) { + return nil, errors.New("not implemented") +} + +func (ds *rewardHistoryStorage) List() (state.Iterator, error) { + return nil, errors.New("not implemented") +} diff --git a/state/factory/erigonstore/workingsetstore_erigon.go b/state/factory/erigonstore/workingsetstore_erigon.go index b852267195..2a6e0b0582 100644 --- a/state/factory/erigonstore/workingsetstore_erigon.go +++ b/state/factory/erigonstore/workingsetstore_erigon.go @@ -47,9 +47,11 @@ type ErigonDB struct { // ErigonWorkingSetStore implements the Erigon working set store type ErigonWorkingSetStore struct { - db *ErigonDB - backend *contractBackend - tx kv.Tx + db *ErigonDB + backend *contractBackend + tx kv.Tx + clean *contractBackend + statesReadBuffer bool } // NewErigonDB creates a new ErigonDB @@ -88,10 +90,13 @@ func (db *ErigonDB) NewErigonStore(ctx context.Context, height uint64) (*ErigonW } r := erigonstate.NewPlainStateReader(tx) intraBlockState := erigonstate.New(r) + g := genesis.MustExtractGenesisContext(ctx) return &ErigonWorkingSetStore{ - db: db, - tx: tx, - backend: newContractBackend(ctx, intraBlockState, r), + db: db, + tx: tx, + backend: newContractBackend(ctx, intraBlockState, r), + clean: newContractBackend(ctx, erigonstate.New(r), r), + statesReadBuffer: g.IsNewfoundland(height), }, nil } @@ -103,10 +108,13 @@ func (db *ErigonDB) NewErigonStoreDryrun(ctx context.Context, height uint64) (*E } tsw := erigonstate.NewPlainState(tx, height, nil) intraBlockState := erigonstate.New(tsw) + g := genesis.MustExtractGenesisContext(ctx) return &ErigonWorkingSetStore{ - db: db, - tx: tx, - backend: newContractBackend(ctx, intraBlockState, tsw), + db: db, + tx: tx, + backend: newContractBackend(ctx, intraBlockState, tsw), + clean: newContractBackend(ctx, erigonstate.New(tsw), tsw), + statesReadBuffer: g.IsNewfoundland(height), }, nil } @@ -337,7 +345,13 @@ func (store *ErigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any) // States gets multiple objects from the ErigonWorkingSetStore func (store *ErigonWorkingSetStore) States(ns string, obj any, keys [][]byte) (state.Iterator, error) { - storage, err := store.NewObjectStorage(ns, obj) + var storage ObjectStorage + var err error + if store.statesReadBuffer { + storage, err = store.NewObjectStorage(ns, obj) + } else { + storage, err = store.NewObjectStorageClean(ns, obj) + } if err != nil { return nil, err } @@ -441,6 +455,19 @@ func (store *ErigonWorkingSetStore) NewObjectStorage(ns string, obj any) (Object } } +func (store *ErigonWorkingSetStore) NewObjectStorageClean(ns string, obj any) (ObjectStorage, error) { + cs, err := storageRegistry.ObjectStorage(ns, obj, store.clean) + switch errors.Cause(err) { + case nil: + return cs, nil + case ErrObjectStorageNotRegistered: + // TODO: fail unknown namespace + return nil, nil + default: + return nil, err + } +} + func (store *ErigonWorkingSetStore) ErigonStore() (any, error) { return store, nil } diff --git a/state/factory/erigonstore/workingsetstore_erigon_test.go b/state/factory/erigonstore/workingsetstore_erigon_test.go new file mode 100644 index 0000000000..34aa2a65b0 --- /dev/null +++ b/state/factory/erigonstore/workingsetstore_erigon_test.go @@ -0,0 +1,185 @@ +package erigonstore + +import ( + "context" + "fmt" + "math/big" + "os" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/rewarding" + "github.com/iotexproject/iotex-core/v2/action/protocol/rewarding/rewardingpb" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/pkg/enc" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/test/identityset" +) + +func TestErigonStoreNativeState(t *testing.T) { + r := require.New(t) + dbpath := "./data/erigonstore_testdb" + r.NoError(os.RemoveAll(dbpath)) + edb := NewErigonDB(dbpath) + r.NoError(edb.Start(context.Background())) + defer func() { + edb.Stop(context.Background()) + }() + g := genesis.TestDefault() + + fmt.Printf("block: %d -----------------------\n", 0) + ctx := context.Background() + ctx = genesis.WithGenesisContext(ctx, g) + ctx = protocol.WithBlockchainCtx(ctx, protocol.BlockchainCtx{}) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: 0}) + ctx = protocol.WithFeatureCtx(ctx) + store, err := edb.NewErigonStore(ctx, 0) + r.NoError(err) + r.NoError(store.CreateGenesisStates(ctx)) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + + height := uint64(1) + t.Run("state.CandidateList", func(t *testing.T) { + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, 1) + r.NoError(err) + candlist := &state.CandidateList{ + &state.Candidate{Address: identityset.Address(1).String(), Votes: big.NewInt(1_000_000_000), RewardAddress: identityset.Address(2).String(), BLSPubKey: []byte("cpc")}, + &state.Candidate{Address: identityset.Address(5).String(), Votes: big.NewInt(2_000_000_000), RewardAddress: identityset.Address(6).String(), BLSPubKey: []byte("iotex")}, + &state.Candidate{Address: identityset.Address(9).String(), Votes: big.NewInt(2_000_000_000), RewardAddress: identityset.Address(10).String(), BLSPubKey: []byte("dev")}, + &state.Candidate{Address: identityset.Address(13).String(), Votes: big.NewInt(2_000_000_000), RewardAddress: identityset.Address(14).String(), BLSPubKey: []byte("test")}, + &state.Candidate{Address: identityset.Address(17).String(), Votes: big.NewInt(2_000_000_000), RewardAddress: identityset.Address(18).String(), BLSPubKey: []byte("prod")}, + } + ns := state.SystemNamespace + key := []byte("key1") + r.NoError(store.PutObject(ns, key, candlist)) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + + height = 2 + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, height) + r.NoError(err) + defer store.Close() + got := &state.CandidateList{} + r.NoError(store.GetObject(ns, key, got)) + r.Equal(candlist, got) + }) + + t.Run("staking.Candidate", func(t *testing.T) { + height++ + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, 1) + r.NoError(err) + candlist := &staking.CandidateList{ + &staking.Candidate{Owner: identityset.Address(1), Operator: identityset.Address(2), Reward: identityset.Address(3), Identifier: nil, BLSPubKey: []byte("pubkey"), Name: "dev", Votes: big.NewInt(1_000_000_000), SelfStake: big.NewInt(100_000_000), SelfStakeBucketIdx: 1}, + &staking.Candidate{Owner: identityset.Address(5), Operator: identityset.Address(6), Reward: identityset.Address(7), Identifier: nil, BLSPubKey: []byte("pubkey2"), Name: "iotex", Votes: big.NewInt(2_000_000_000), SelfStake: big.NewInt(200_000_000), SelfStakeBucketIdx: 2}, + &staking.Candidate{Owner: identityset.Address(9), Operator: identityset.Address(10), Reward: identityset.Address(11), Identifier: nil, BLSPubKey: []byte("pubkey3"), Name: "test", Votes: big.NewInt(3_000_000_000), SelfStake: big.NewInt(300_000_000), SelfStakeBucketIdx: 3}, + &staking.Candidate{Owner: identityset.Address(13), Operator: identityset.Address(14), Reward: identityset.Address(15), Identifier: nil, BLSPubKey: []byte("pubkey4"), Name: "prod", Votes: big.NewInt(4_000_000_000), SelfStake: big.NewInt(400_000_000), SelfStakeBucketIdx: 4}, + &staking.Candidate{Owner: identityset.Address(17), Operator: identityset.Address(18), Reward: identityset.Address(19), Identifier: nil, BLSPubKey: []byte("pubkey5"), Name: "cpc", Votes: big.NewInt(5_000_000_000), SelfStake: big.NewInt(500_000_000), SelfStakeBucketIdx: 5}, + } + ns := state.CandsMapNamespace + key := []byte("key1") + r.NoError(store.PutObject(ns, key, candlist)) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + + height++ + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, height) + r.NoError(err) + defer store.Close() + got := &staking.CandidateList{} + r.NoError(store.GetObject(ns, key, got)) + r.Equal(candlist, got) + }) + + t.Run("rewarding.RewardHistory", func(t *testing.T) { + height++ + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, height) + r.NoError(err) + defer store.Close() + rh := &rewarding.RewardHistory{} + ns := state.AccountKVNamespace + var indexBytes [8]byte + enc.MachineEndian.PutUint64(indexBytes[:], height) + key := append(state.RewardingKeyPrefix[:], state.BlockRewardHistoryKeyPrefix...) + key = append(key, indexBytes[:]...) + nextKey := append(state.RewardingKeyPrefix[:], state.BlockRewardHistoryKeyPrefix...) + enc.MachineEndian.PutUint64(indexBytes[:], height+1) + nextKey = append(nextKey, indexBytes[:]...) + // test GetObject/DeleteObject/PutObject + r.NoError(store.GetObject(ns, key, rh)) + r.ErrorIs(store.GetObject(ns, nextKey, rh), state.ErrStateNotExist) + r.NoError(store.DeleteObject(ns, key, rh)) + r.ErrorIs(store.GetObject(ns, key, rh), state.ErrStateNotExist) + r.NoError(store.PutObject(ns, key, rh)) + r.NoError(store.GetObject(ns, key, rh)) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + }) + + t.Run("rewarding.Fund", func(t *testing.T) { + height++ + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, height) + r.NoError(err) + defer store.Close() + rh := &rewarding.Fund{} + rhpb := &rewardingpb.Fund{ + TotalBalance: "1000000000000000000", + UnclaimedBalance: "500000000000000000", + } + data, err := proto.Marshal(rhpb) + r.NoError(err) + r.NoError(rh.Deserialize(data)) + ns := state.AccountKVNamespace + _fundKey := []byte("fnd") + key := append(state.RewardingKeyPrefix[:], _fundKey...) + r.NoError(store.PutObject(ns, key, rh)) + got := &rewarding.Fund{} + r.NoError(store.GetObject(ns, key, got)) + r.EqualValues(rh, got) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + + height++ + fmt.Printf("block: %d -----------------------\n", height) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}) + ctx = protocol.WithFeatureCtx(ctx) + store, err = edb.NewErigonStore(ctx, height) + r.NoError(err) + defer store.Close() + rhpb = &rewardingpb.Fund{ + TotalBalance: "1000000000000000000", + UnclaimedBalance: "400000000000000000", + } + data, err = proto.Marshal(rhpb) + r.NoError(err) + r.NoError(rh.Deserialize(data)) + r.NoError(store.PutObject(ns, key, rh)) + got = &rewarding.Fund{} + r.NoError(store.GetObject(ns, key, got)) + r.EqualValues(rh, got) + r.NoError(store.FinalizeTx(ctx)) + r.NoError(store.Commit(ctx, 0)) + }) +} diff --git a/state/iterator.go b/state/iterator.go index 60d5a407e0..2f59597256 100644 --- a/state/iterator.go +++ b/state/iterator.go @@ -51,7 +51,7 @@ func (it *iterator) Next(s interface{}) ([]byte, error) { } it.index = i + 1 if it.states[i] == nil { - return nil, ErrNilValue + return it.keys[i], ErrNilValue } return it.keys[i], Deserialize(s, it.states[i]) } diff --git a/systemcontractindex/stakingindex/candidate_votes.go b/systemcontractindex/stakingindex/candidate_votes.go index 921ff1a40e..054d876e07 100644 --- a/systemcontractindex/stakingindex/candidate_votes.go +++ b/systemcontractindex/stakingindex/candidate_votes.go @@ -138,7 +138,7 @@ func (cv *candidateVotes) Deserialize(data []byte) error { return nil } -func (cv *candidateVotes) Encode() ([][]byte, []systemcontracts.GenericValue, error) { +func (cv *candidateVotes) Encodes() ([][]byte, []systemcontracts.GenericValue, error) { var ( keys [][]byte values []systemcontracts.GenericValue @@ -161,7 +161,7 @@ func (cv *candidateVotes) Encode() ([][]byte, []systemcontracts.GenericValue, er return keys, values, nil } -func (cv *candidateVotes) Decode(keys [][]byte, values []systemcontracts.GenericValue) error { +func (cv *candidateVotes) Decodes(keys [][]byte, values []systemcontracts.GenericValue) error { ncv := newCandidateVotes() for i, key := range keys { cand := string(key) @@ -329,14 +329,14 @@ func (cv *candidateVotesWithBuffer) Base() CandidateVotes { return newCandidateVotesWithBuffer(cv.base) } -func (cv *candidateVotesWithBuffer) Encode() ([][]byte, []systemcontracts.GenericValue, error) { +func (cv *candidateVotesWithBuffer) Encodes() ([][]byte, []systemcontracts.GenericValue, error) { if cv.IsDirty() { return nil, nil, errors.Wrap(ErrCandidateVotesIsDirty, "cannot encode dirty candidate votes") } - return cv.base.Encode() + return cv.base.Encodes() } -func (cv *candidateVotesWithBuffer) Decode(keys [][]byte, data []systemcontracts.GenericValue) error { +func (cv *candidateVotesWithBuffer) Decodes(keys [][]byte, data []systemcontracts.GenericValue) error { cv.change = newCandidateVotes() - return cv.base.Decode(keys, data) + return cv.base.Decodes(keys, data) } diff --git a/systemcontractindex/stakingindex/voteview.go b/systemcontractindex/stakingindex/voteview.go index d6e852f535..b8548f1b5d 100644 --- a/systemcontractindex/stakingindex/voteview.go +++ b/systemcontractindex/stakingindex/voteview.go @@ -176,8 +176,9 @@ func (s *voteView) AddBlockReceipts(ctx context.Context, receipts []*action.Rece } func (s *voteView) Commit(ctx context.Context, sm protocol.StateManager) error { + isDirty := s.cur.IsDirty() s.cur = s.cur.Commit() - if sm == nil { + if sm == nil || !isDirty { return nil } return s.cvm.Store(ctx, sm, s.cur)