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/execution/evm/evm.go b/action/protocol/execution/evm/evm.go index b6a68fd1b5..c118cc4e89 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 { @@ -762,3 +760,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/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/action/protocol/protocol.go b/action/protocol/protocol.go index 2e89130873..f2c96bf070 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/admin.go b/action/protocol/rewarding/admin.go index 47b2302030..f388f6c9ee 100644 --- a/action/protocol/rewarding/admin.go +++ b/action/protocol/rewarding/admin.go @@ -13,9 +13,14 @@ import ( "google.golang.org/protobuf/proto" "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/pkg/util/assertions" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // admin stores the admin data of the rewarding protocol @@ -29,6 +34,14 @@ type admin struct { productivityThreshold uint64 } +func init() { + registry := erigonstore.GetObjectStorageRegistry() + assertions.MustNoError(registry.RegisterRewardingV1(state.AccountKVNamespace, &admin{})) + assertions.MustNoError(registry.RegisterRewardingV1(state.AccountKVNamespace, &exempt{})) + assertions.MustNoError(registry.RegisterRewardingV2(_v2RewardingNamespace, &admin{})) + assertions.MustNoError(registry.RegisterRewardingV2(_v2RewardingNamespace, &exempt{})) +} + // Serialize serializes admin state into bytes func (a admin) Serialize() ([]byte, error) { gen := rewardingpb.Admin{ @@ -71,6 +84,20 @@ func (a *admin) Deserialize(data []byte) error { return nil } +func (a *admin) Encode() (systemcontracts.GenericValue, error) { + data, err := a.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, err + } + return systemcontracts.GenericValue{ + AuxiliaryData: data, + }, nil +} + +func (a *admin) Decode(v systemcontracts.GenericValue) error { + return a.Deserialize(v.AuxiliaryData) +} + func (a *admin) grantFoundationBonus(epoch uint64) bool { return epoch <= a.foundationBonusLastEpoch } @@ -106,6 +133,20 @@ func (e *exempt) Deserialize(data []byte) error { return nil } +func (e *exempt) Encode() (systemcontracts.GenericValue, error) { + data, err := e.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, err + } + return systemcontracts.GenericValue{ + AuxiliaryData: data, + }, nil +} + +func (e *exempt) Decode(v systemcontracts.GenericValue) error { + return e.Deserialize(v.AuxiliaryData) +} + // 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..95bbc49d0f 100644 --- a/action/protocol/rewarding/fund.go +++ b/action/protocol/rewarding/fund.go @@ -19,7 +19,10 @@ import ( "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/rewarding/rewardingpb" + "github.com/iotexproject/iotex-core/v2/pkg/util/assertions" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" + "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,12 @@ type fund struct { unclaimedBalance *big.Int } +func init() { + registry := erigonstore.GetObjectStorageRegistry() + assertions.MustNoError(registry.RegisterRewardingV1(state.AccountKVNamespace, &fund{})) + assertions.MustNoError(registry.RegisterRewardingV2(_v2RewardingNamespace, &fund{})) +} + // Serialize serializes fund state into bytes func (f fund) Serialize() ([]byte, error) { gen := rewardingpb.Fund{ @@ -57,6 +66,20 @@ func (f *fund) Deserialize(data []byte) error { return nil } +func (f *fund) Encode() (systemcontracts.GenericValue, error) { + data, err := f.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, err + } + return systemcontracts.GenericValue{ + AuxiliaryData: data, + }, nil +} + +func (f *fund) Decode(v systemcontracts.GenericValue) error { + return f.Deserialize(v.AuxiliaryData) +} + // 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 258026a731..4ed8190582 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 ( @@ -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..76346217b3 100644 --- a/action/protocol/rewarding/reward.go +++ b/action/protocol/rewarding/reward.go @@ -25,12 +25,23 @@ import ( "github.com/iotexproject/iotex-core/v2/action/protocol/staking" "github.com/iotexproject/iotex-core/v2/pkg/enc" "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/pkg/util/assertions" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" + "github.com/iotexproject/iotex-core/v2/systemcontracts" ) // rewardHistory is the dummy struct to record a reward. Only key matters. type rewardHistory struct{} +func init() { + registry := erigonstore.GetObjectStorageRegistry() + assertions.MustNoError(registry.RegisterRewardingV1(state.AccountKVNamespace, &rewardHistory{})) + assertions.MustNoError(registry.RegisterRewardingV1(state.AccountKVNamespace, &rewardAccount{})) + assertions.MustNoError(registry.RegisterRewardingV2(_v2RewardingNamespace, &rewardHistory{})) + assertions.MustNoError(registry.RegisterRewardingV2(_v2RewardingNamespace, &rewardAccount{})) +} + // Serialize serializes reward history state into bytes func (b rewardHistory) Serialize() ([]byte, error) { gen := rewardingpb.RewardHistory{} @@ -40,6 +51,20 @@ 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) Encode() (systemcontracts.GenericValue, error) { + data, err := b.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, err + } + return systemcontracts.GenericValue{ + AuxiliaryData: data, + }, nil +} + +func (b *rewardHistory) Decode(v systemcontracts.GenericValue) error { + return b.Deserialize(v.AuxiliaryData) +} + // rewardAccount stores the unclaimed balance of an account type rewardAccount struct { balance *big.Int @@ -67,6 +92,20 @@ func (a *rewardAccount) Deserialize(data []byte) error { return nil } +func (a *rewardAccount) Encode() (systemcontracts.GenericValue, error) { + data, err := a.Serialize() + if err != nil { + return systemcontracts.GenericValue{}, err + } + return systemcontracts.GenericValue{ + AuxiliaryData: data, + }, nil +} + +func (a *rewardAccount) Decode(v systemcontracts.GenericValue) error { + return a.Deserialize(v.AuxiliaryData) +} + // GrantBlockReward grants the block reward (token) to the block producer func (p *Protocol) GrantBlockReward( ctx context.Context, @@ -389,7 +428,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 +455,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 } } diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index 3436021c0c..db0478cdba 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/api/coreservice.go b/api/coreservice.go index 872154ad6f..5ec42406a5 100644 --- a/api/coreservice.go +++ b/api/coreservice.go @@ -359,10 +359,19 @@ func (core *coreService) BalanceAt(ctx context.Context, addr address.Address, he ctx, span := tracer.NewSpan(context.Background(), "coreService.BalanceAt") defer span.End() addrStr := addr.String() + if height == 0 { + height = core.bc.TipHeight() + } ctx, err := core.bc.ContextAtHeight(ctx, height) if err != nil { return "", status.Error(codes.Internal, err.Error()) } + bcCtx := protocol.MustGetBlockchainCtx(ctx) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bcCtx.Tip.Height, + BlockTimeStamp: bcCtx.Tip.Timestamp, + }) + ctx = protocol.WithFeatureCtx(ctx) if addrStr == address.RewardingPoolAddr || addrStr == address.StakingBucketPoolAddr || addrStr == address.RewardingProtocol || addrStr == address.StakingProtocolAddr { acc, _, err := core.getProtocolAccount(ctx, addrStr) @@ -372,9 +381,6 @@ func (core *coreService) BalanceAt(ctx context.Context, addr address.Address, he return acc.Balance, nil } - if height == 0 { - height = core.bc.TipHeight() - } ws, err := core.sf.WorkingSetAtHeight(ctx, height) if err != nil { return "", status.Error(codes.Internal, err.Error()) @@ -2195,12 +2201,22 @@ func (core *coreService) simulateExecution( if err != nil { return nil, nil, status.Error(codes.Internal, err.Error()) } + bcCtx := protocol.MustGetBlockchainCtx(ctx) + ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bcCtx.Tip.Height, + BlockTimeStamp: bcCtx.Tip.Timestamp, + })) ws, err = core.sf.WorkingSetAtHeight(ctx, height) } else { ctx, err = core.bc.Context(ctx) if err != nil { return nil, nil, status.Error(codes.Internal, err.Error()) } + bcCtx := protocol.MustGetBlockchainCtx(ctx) + ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bcCtx.Tip.Height, + BlockTimeStamp: bcCtx.Tip.Timestamp, + })) ws, err = core.sf.WorkingSet(ctx) } if err != nil { @@ -2212,9 +2228,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() @@ -2238,6 +2251,12 @@ func (core *coreService) workingSetAt(ctx context.Context, height uint64) (conte if err != nil { return ctx, nil, status.Error(codes.Internal, err.Error()) } + bcCtx := protocol.MustGetBlockchainCtx(ctx) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bcCtx.Tip.Height, + BlockTimeStamp: bcCtx.Tip.Timestamp, + }) + ctx = protocol.WithFeatureCtx(ctx) ws, err := core.sf.WorkingSetAtHeight(ctx, height) if err != nil { return ctx, nil, status.Error(codes.Internal, err.Error()) diff --git a/api/coreservice_test.go b/api/coreservice_test.go index afbef43af1..26994f3fb5 100644 --- a/api/coreservice_test.go +++ b/api/coreservice_test.go @@ -492,6 +492,9 @@ func TestEstimateExecutionGasConsumption(t *testing.T) { bc.EXPECT().Genesis().Return(genesis.Genesis{}).Times(1) bc.EXPECT().TipHeight().Return(uint64(1)).Times(2) + ctx := protocol.WithBlockchainCtx(genesis.WithGenesisContext(ctx, genesis.Genesis{}), protocol.BlockchainCtx{ + Tip: protocol.TipInfo{Height: 1, Timestamp: time.Now()}, + }) bc.EXPECT().ContextAtHeight(gomock.Any(), gomock.Any()).Return(ctx, nil).Times(1) sf.EXPECT().WorkingSetAtHeight(gomock.Any(), gomock.Any()).Return(&mockWS{}, nil).Times(1) elp := (&action.EnvelopeBuilder{}).SetAction(&action.Execution{}).Build() diff --git a/go.mod b/go.mod index aa23e34054..e096d2e99a 100644 --- a/go.mod +++ b/go.mod @@ -241,7 +241,7 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gopacket v1.1.19 // indirect diff --git a/pkg/util/assertions/must.go b/pkg/util/assertions/must.go index 5a6e6e91f8..0f83d5fe14 100644 --- a/pkg/util/assertions/must.go +++ b/pkg/util/assertions/must.go @@ -1,5 +1,11 @@ package assertions +func MustNoError(err error) { + if err != nil { + panic(err) + } +} + func MustNoErrorV[V any](v V, err error) V { if err != nil { panic(err) diff --git a/state/factory/erigonstore/accountstorage.go b/state/factory/erigonstore/accountstorage.go new file mode 100644 index 0000000000..68eecc1181 --- /dev/null +++ b/state/factory/erigonstore/accountstorage.go @@ -0,0 +1,129 @@ +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" + "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/v2/action/protocol/account/accountpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type accountStorage struct { + backend *contractBackend + contract systemcontracts.StorageContract +} + +func newAccountStorage(addr common.Address, backend *contractBackend) (*accountStorage, error) { + contract, err := systemcontracts.NewGenericStorageContract( + addr, + backend, + common.Address(systemContractCreatorAddr), + ) + if err != nil { + return nil, errors.Wrap(err, "failed to create account storage contract") + } + return &accountStorage{ + backend: backend, + contract: contract, + }, nil +} + +func (as *accountStorage) Delete([]byte) error { + return errors.New("not implemented") +} + +func (as *accountStorage) Batch([][]byte) (state.Iterator, error) { + return nil, errors.New("not implemented") +} + +func (as *accountStorage) List() (state.Iterator, error) { + return nil, errors.New("not implemented") +} + +func (as *accountStorage) Load(key []byte, obj any) error { + addr := erigonComm.BytesToAddress(key) + acct, ok := obj.(*state.Account) + if !ok { + return errors.New("obj is not of type *state.Account") + } + if !as.backend.intraBlockState.Exist(addr) { + return errors.Wrapf(state.ErrStateNotExist, "address: %x", addr.Bytes()) + } + // load other fields from the account storage contract + value, err := as.contract.Get(addr.Bytes()) + if err != nil { + return errors.Wrapf(err, "failed to get account data for address %x", addr.Bytes()) + } + if !value.KeyExists { + return errors.Errorf("account info not found for address %x", addr.Bytes()) + } + pbAcc := &accountpb.Account{} + if err := proto.Unmarshal(value.Value.PrimaryData, pbAcc); err != nil { + return errors.Wrapf(err, "failed to unmarshal account data for address %x", addr.Bytes()) + } + + balance := as.backend.intraBlockState.GetBalance(addr) + nonce := as.backend.intraBlockState.GetNonce(addr) + pbAcc.Balance = balance.String() + switch pbAcc.Type { + case accountpb.AccountType_ZERO_NONCE: + pbAcc.Nonce = nonce + case accountpb.AccountType_DEFAULT: + 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() + } + acct.FromProto(pbAcc) + return nil +} + +func (as *accountStorage) Exists(key []byte) (bool, error) { + return as.exists(key) +} + +func (as *accountStorage) exists(key []byte) (bool, error) { + addr := erigonComm.BytesToAddress(key) + if !as.backend.intraBlockState.Exist(addr) { + return false, nil + } + return true, nil +} + +func (as *accountStorage) Store(key []byte, value any) error { + if value == nil { + return errors.New("value is nil") + } + acc, ok := value.(*state.Account) + if !ok { + return errors.New("value is not of type *state.Account") + } + addr := erigonComm.BytesToAddress(key) + if !as.backend.intraBlockState.Exist(addr) { + as.backend.intraBlockState.CreateAccount(addr, acc.IsContract()) + } + as.backend.intraBlockState.SetBalance(addr, uint256.MustFromBig(acc.Balance)) + nonce := acc.PendingNonce() + if as.backend.useZeroNonceForFreshAccount { + nonce = acc.PendingNonceConsideringFreshAccount() + } + as.backend.intraBlockState.SetNonce(addr, nonce) + // store other fields in the account storage contract + pbAcc := acc.ToProto() + pbAcc.Balance = "" + pbAcc.Nonce = 0 + data, err := proto.Marshal(pbAcc) + if err != nil { + return errors.Wrapf(err, "failed to marshal account %x", addr.Bytes()) + } + + return as.contract.Put(key, systemcontracts.GenericValue{PrimaryData: data}) +} diff --git a/state/factory/erigonstore/contract_backend.go b/state/factory/erigonstore/contract_backend.go new file mode 100644 index 0000000000..093fd95584 --- /dev/null +++ b/state/factory/erigonstore/contract_backend.go @@ -0,0 +1,282 @@ +package erigonstore + +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 ( + contractBackend struct { + intraBlockState *erigonstate.IntraBlockState + org erigonstate.StateReader + + // helper fields + height uint64 + timestamp time.Time + producer erigonComm.Address + g *genesis.Genesis + evmNetworkID uint32 + + useZeroNonceForFreshAccount bool + } +) + +// NewContractBackend creates a new contract backend for system contract interaction +func NewContractBackend(intraBlockState *erigonstate.IntraBlockState, org erigonstate.StateReader, height uint64, timestamp time.Time, producer address.Address, g *genesis.Genesis, evmNetworkID uint32, useZeroNonceForFreshAccount bool) *contractBackend { + var producerAddr erigonComm.Address + if producer != nil { + producerAddr = erigonComm.BytesToAddress(producer.Bytes()) + } + return &contractBackend{ + intraBlockState: intraBlockState, + org: org, + height: height, + timestamp: timestamp, + g: g, + evmNetworkID: evmNetworkID, + producer: producerAddr, + useZeroNonceForFreshAccount: useZeroNonceForFreshAccount, + } +} + +func (backend *contractBackend) Call(callMsg *ethereum.CallMsg) ([]byte, error) { + return backend.call(callMsg, erigonstate.New(&intraStateReader{backend.intraBlockState, backend.org})) +} + +func (backend *contractBackend) Handle(callMsg *ethereum.CallMsg) error { + _, err := backend.call(callMsg, backend.intraBlockState) + return err +} + +func (backend *contractBackend) 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().Debug("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 *contractBackend) Exists(addr address.Address) bool { + return backend.intraBlockState.Exist(erigonComm.BytesToAddress(addr.Bytes())) +} + +func (backend *contractBackend) prepare(intra evmtypes.IntraBlockState) (*vm.EVM, error) { + 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: backend.producer, + 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) { + if u == backend.height { + return backend.timestamp, nil + } + return time.Time{}, errors.New("only current block is supported") + }, + 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 *contractBackend) 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().Debug("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 +} diff --git a/state/factory/erigonstore/objectstorage.go b/state/factory/erigonstore/objectstorage.go new file mode 100644 index 0000000000..dfc9a7a0b9 --- /dev/null +++ b/state/factory/erigonstore/objectstorage.go @@ -0,0 +1,86 @@ +package erigonstore + +import ( + "math/big" + + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +type ( + // ObjectStorage defines an interface of object storage + ObjectStorage interface { + Store(key []byte, obj any) error + Load(key []byte, obj any) error + Delete(key []byte) error + List() (state.Iterator, error) + Batch(keys [][]byte) (state.Iterator, error) + } + + contractObjectStorage struct { + contract systemcontracts.StorageContract + } +) + +func newContractObjectStorage(contract systemcontracts.StorageContract) *contractObjectStorage { + return &contractObjectStorage{ + contract: contract, + } +} + +func (cos *contractObjectStorage) Store(key []byte, obj any) error { + gvc, ok := obj.(systemcontracts.GenericValueContainer) + if !ok { + return errors.New("object does not implement GenericValueContainer") + } + value, err := gvc.Encode() + if err != nil { + return err + } + return cos.contract.Put(key, value) +} + +func (cos *contractObjectStorage) Load(key []byte, obj any) error { + gvc, ok := obj.(systemcontracts.GenericValueContainer) + if !ok { + return errors.New("object does not implement GenericValueContainer") + } + value, err := cos.contract.Get(key) + if err != nil { + return err + } + // TODO: handle value.KeyExists + return gvc.Decode(value.Value) +} + +func (cos *contractObjectStorage) Delete(key []byte) error { + return cos.contract.Remove(key) +} + +func (cos *contractObjectStorage) List() (state.Iterator, error) { + count, err := cos.contract.Count() + if err != nil { + return nil, err + } + retval, err := cos.contract.List(0, count.Uint64()) + if err != nil { + return nil, err + } + + return systemcontracts.NewGenericValueObjectIterator(retval.KeyList, retval.Values, nil) +} + +func (cos *contractObjectStorage) Batch(keys [][]byte) (state.Iterator, error) { + retval, err := cos.contract.BatchGet(keys) + if err != nil { + return nil, err + } + + return systemcontracts.NewGenericValueObjectIterator(keys, retval.Values, retval.ExistsFlags) +} + +func (cos *contractObjectStorage) Count() (*big.Int, error) { + return cos.contract.Count() +} diff --git a/state/factory/erigonstore/registry.go b/state/factory/erigonstore/registry.go new file mode 100644 index 0000000000..7623f1c30e --- /dev/null +++ b/state/factory/erigonstore/registry.go @@ -0,0 +1,168 @@ +package erigonstore + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +var ( + // ErrObjectStorageNotRegistered is returned when an object storage is not registered + ErrObjectStorageNotRegistered = errors.New("object storage not registered") + // ErrObjectStorageAlreadyRegistered is returned when an object storage is already registered + ErrObjectStorageAlreadyRegistered = errors.New("object storage already registered") +) + +var ( + storageRegistry = newObjectStorageRegistry() +) + +// ObjectStorageRegistry is a registry for object storage +type ObjectStorageRegistry struct { + contracts map[string]map[reflect.Type]int +} + +func init() { + if err := storageRegistry.RegisterAccount(state.AccountKVNamespace, &state.Account{}); err != nil { + log.L().Panic("failed to register account storage", zap.Error(err)) + } +} + +// GetObjectStorageRegistry returns the global object storage registry +func GetObjectStorageRegistry() *ObjectStorageRegistry { + return storageRegistry +} + +func newObjectStorageRegistry() *ObjectStorageRegistry { + return &ObjectStorageRegistry{ + contracts: make(map[string]map[reflect.Type]int), + } +} + +// ObjectStorage returns the object storage for the given namespace and object type +func (osr *ObjectStorageRegistry) ObjectStorage(ns string, obj any, backend *contractBackend) (ObjectStorage, error) { + types, ok := osr.contracts[ns] + if !ok { + return nil, errors.Wrapf(ErrObjectStorageNotRegistered, "namespace: %s", ns) + } + contractIndex, ok := types[reflect.TypeOf(obj)] + if !ok { + return nil, errors.Wrapf(ErrObjectStorageNotRegistered, "namespace: %s, object: %T", ns, obj) + } + // TODO: cache storage + switch systemContractTypes[contractIndex] { + case accountStorageType: + return newAccountStorage( + common.BytesToAddress(systemContracts[AccountInfoContractIndex].Address.Bytes()), + backend, + ) + case namespaceStorageContractType: + contractAddr := systemContracts[contractIndex].Address + contract, err := systemcontracts.NewGenericStorageContract(common.BytesToAddress(contractAddr.Bytes()[:]), backend, common.Address(systemContractCreatorAddr)) + if err != nil { + return nil, err + } + return newContractObjectStorage(contract), nil + default: + contractAddr := systemContracts[contractIndex].Address + contract, err := systemcontracts.NewGenericStorageContract(common.BytesToAddress(contractAddr.Bytes()[:]), backend, common.Address(systemContractCreatorAddr)) + if err != nil { + return nil, err + } + return newContractObjectStorage(contract), nil + } +} + +// RegisterAccount registers an account object storage +func (osr *ObjectStorageRegistry) RegisterAccount(ns string, obj any) error { + return osr.register(ns, obj, AccountIndex) +} + +// RegisterStakingBuckets registers a staking buckets object storage +func (osr *ObjectStorageRegistry) RegisterStakingBuckets(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, StakingBucketsContractIndex) +} + +// RegisterBucketPool registers a bucket pool object storage +func (osr *ObjectStorageRegistry) RegisterBucketPool(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, BucketPoolContractIndex) +} + +// RegisterBucketIndices registers a bucket indices object storage +func (osr *ObjectStorageRegistry) RegisterBucketIndices(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, BucketIndicesContractIndex) +} + +// RegisterEndorsement registers an endorsement object storage +func (osr *ObjectStorageRegistry) RegisterEndorsement(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, EndorsementContractIndex) +} + +// RegisterCandidateMap registers a candidate map object storage +func (osr *ObjectStorageRegistry) RegisterCandidateMap(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, CandidateMapContractIndex) +} + +// RegisterCandidates registers a candidates object storage +func (osr *ObjectStorageRegistry) RegisterCandidates(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, CandidatesContractIndex) +} + +// RegisterPollCandidateList registers a poll candidate list object storage +func (osr *ObjectStorageRegistry) RegisterPollCandidateList(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, PollCandidateListContractIndex) +} + +// RegisterPollLegacyCandidateList registers a poll legacy candidate list object storage +func (osr *ObjectStorageRegistry) RegisterPollLegacyCandidateList(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, PollLegacyCandidateListContractIndex) +} + +// RegisterPollProbationList registers a poll probation list object storage +func (osr *ObjectStorageRegistry) RegisterPollProbationList(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, PollProbationListContractIndex) +} + +// RegisterPollUnproductiveDelegate registers a poll unproductive delegate object storage +func (osr *ObjectStorageRegistry) RegisterPollUnproductiveDelegate(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, PollUnproductiveDelegateContractIndex) +} + +// RegisterPollBlockMeta registers a poll block meta object storage +func (osr *ObjectStorageRegistry) RegisterPollBlockMeta(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, PollBlockMetaContractIndex) +} + +// RegisterRewardingV1 registers a rewarding v1 object storage +func (osr *ObjectStorageRegistry) RegisterRewardingV1(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, RewardingContractV1Index) +} + +// RegisterRewardingV2 registers a rewarding v2 object storage +func (osr *ObjectStorageRegistry) RegisterRewardingV2(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, RewardingContractV2Index) +} + +// RegisterStakingView registers a staking view object storage +func (osr *ObjectStorageRegistry) RegisterStakingView(ns string, obj systemcontracts.GenericValueContainer) error { + return osr.register(ns, obj, StakingViewContractIndex) +} + +func (osr *ObjectStorageRegistry) register(ns string, obj any, index int) error { + types, ok := osr.contracts[ns] + if !ok { + osr.contracts[ns] = make(map[reflect.Type]int) + types = osr.contracts[ns] + } + if registered, exists := types[reflect.TypeOf(obj)]; exists { + return errors.Wrapf(ErrObjectStorageAlreadyRegistered, "registered: %v", registered) + } + types[reflect.TypeOf(obj)] = index + return nil +} diff --git a/state/factory/erigonstore/systemcontracts.go b/state/factory/erigonstore/systemcontracts.go new file mode 100644 index 0000000000..c9a627b9bf --- /dev/null +++ b/state/factory/erigonstore/systemcontracts.go @@ -0,0 +1,105 @@ +package erigonstore + +import ( + "encoding/hex" + "log" + + "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon-lib/crypto" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/systemcontracts" +) + +// SystemContract represents a system contract with its address and bytecode +type SystemContract struct { + Address address.Address + Code []byte +} + +const ( + // AccountIndex is the system contract for account storage + AccountIndex = -1 + // 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 + // AccountInfoContractIndex is the system contract for account info storage + AccountInfoContractIndex + // SystemContractCount is the total number of system contracts + SystemContractCount +) + +const ( + defaultSystemContractType = iota + namespaceStorageContractType + accountStorageType +) + +var systemContractTypes = map[int]int{ + StakingViewContractIndex: namespaceStorageContractType, + AccountIndex: accountStorageType, +} + +// systemContracts holds all system contracts +var systemContracts []SystemContract + +// systemContractCreatorAddr is the address used to create system contracts +// TODO: review and delete it +var systemContractCreatorAddr = hash.Hash160b([]byte("system_contract_creator")) + +func init() { + genericStorageByteCode, err := hex.DecodeString(systemcontracts.GenericStorageByteCodeStr) + if err != nil { + log.Panic(errors.Wrap(err, "failed to decode GenericStorageByteCode")) + } + namespaceStorageByteCode, err := hex.DecodeString(systemcontracts.NamespaceStorageContractByteCodeStr) + if err != nil { + log.Panic(errors.Wrap(err, "failed to decode NamespaceStorageContractByteCode")) + } + + 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.Panic(errors.Wrap(err, "invalid system contract address")) + } + var byteCode []byte + switch systemContractTypes[i] { + case namespaceStorageContractType: + byteCode = namespaceStorageByteCode + default: + byteCode = genericStorageByteCode + } + systemContracts[i] = SystemContract{ + Address: addr, + Code: byteCode, + } + } +} diff --git a/state/factory/erigonstore/workingsetstore_erigon.go b/state/factory/erigonstore/workingsetstore_erigon.go new file mode 100644 index 0000000000..62f482444c --- /dev/null +++ b/state/factory/erigonstore/workingsetstore_erigon.go @@ -0,0 +1,442 @@ +package erigonstore + +import ( + "context" + "fmt" + "math/big" + + "github.com/erigontech/erigon-lib/common/datadir" + "github.com/erigontech/erigon-lib/kv" + "github.com/erigontech/erigon-lib/kv/mdbx" + "github.com/erigontech/erigon-lib/kv/temporal/historyv2" + erigonlog "github.com/erigontech/erigon-lib/log/v3" + erigonstate "github.com/erigontech/erigon/core/state" + "github.com/erigontech/erigon/eth/ethconfig" + "github.com/erigontech/erigon/eth/stagedsync" + "github.com/erigontech/erigon/eth/stagedsync/stages" + "github.com/erigontech/erigon/ethdb/prune" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/iotexproject/go-pkgs/hash" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" + "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/pkg/log" + "github.com/iotexproject/iotex-core/v2/state" +) + +const ( + systemNS = "erigonsystem" +) + +var ( + heightKey = []byte("height") +) + +// ErigonDB implements the Erigon database +type ErigonDB struct { + path string + rw kv.RwDB +} + +// ErigonWorkingSetStore implements the Erigon working set store +type ErigonWorkingSetStore struct { + db *ErigonDB + backend *contractBackend + tx kv.Tx +} + +// NewErigonDB creates a new ErigonDB +func NewErigonDB(path string) *ErigonDB { + return &ErigonDB{path: path} +} + +// Start starts the ErigonDB +func (db *ErigonDB) Start(ctx context.Context) error { + log.L().Info("starting history state index") + lg := erigonlog.New() + lg.SetHandler(erigonlog.StdoutHandler) + rw, err := mdbx.NewMDBX(lg).Path(db.path).WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { + defaultBuckets[systemNS] = kv.TableCfgItem{} + return defaultBuckets + }).Open(ctx) + if err != nil { + return errors.Wrap(err, "failed to open history state index") + } + db.rw = rw + return nil +} + +// Stop stops the ErigonDB +func (db *ErigonDB) Stop(ctx context.Context) { + if db.rw != nil { + db.rw.Close() + } +} + +// NewErigonStore creates a new ErigonWorkingSetStore +func (db *ErigonDB) NewErigonStore(ctx context.Context, height uint64) (*ErigonWorkingSetStore, error) { + tx, err := db.rw.BeginRo(ctx) + if err != nil { + return nil, err + } + r := erigonstate.NewPlainStateReader(tx) + intraBlockState := erigonstate.New(r) + return &ErigonWorkingSetStore{ + db: db, + tx: tx, + backend: newContractBackend(ctx, intraBlockState, r), + }, nil +} + +// NewErigonStoreDryrun creates a new ErigonWorkingSetStore for dryrun +func (db *ErigonDB) NewErigonStoreDryrun(ctx context.Context, height uint64) (*ErigonWorkingSetStore, error) { + tx, err := db.rw.BeginRo(ctx) + if err != nil { + return nil, err + } + tsw := erigonstate.NewPlainState(tx, height, nil) + intraBlockState := erigonstate.New(tsw) + return &ErigonWorkingSetStore{ + db: db, + tx: tx, + backend: newContractBackend(ctx, intraBlockState, tsw), + }, nil +} + +// BatchPrune prunes the ErigonWorkingSetStore in batches +func (db *ErigonDB) BatchPrune(ctx context.Context, from, to, batch uint64) error { + if from >= to { + return errors.Errorf("invalid prune range: from %d >= to %d", from, to) + } + tx, err := db.rw.BeginRo(ctx) + if err != nil { + return errors.Wrap(err, "failed to begin erigon working set store transaction") + } + base, err := historyv2.AvailableFrom(tx) + if err != nil { + tx.Rollback() + return errors.Wrap(err, "failed to get available from erigon working set store") + } + tx.Rollback() + + if base >= from { + log.L().Debug("batch prune nothing", zap.Uint64("from", from), zap.Uint64("to", to), zap.Uint64("base", base)) + // nothing to prune + return nil + } + + for batchFrom := base; batchFrom < from; batchFrom += batch { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + log.L().Info("batch prune", zap.Uint64("from", batchFrom), zap.Uint64("to", to)) + if err = db.Prune(ctx, nil, batchFrom, to); err != nil { + return err + } + } + log.L().Info("batch prune", zap.Uint64("from", from), zap.Uint64("to", to)) + return db.Prune(ctx, nil, from, to) +} + +// Prune prunes the ErigonWorkingSetStore from 'from' to 'to' +func (db *ErigonDB) Prune(ctx context.Context, tx kv.RwTx, from, to uint64) error { + if from >= to { + return errors.Errorf("invalid prune range: from %d >= to %d", from, to) + } + log.L().Debug("erigon working set store prune execution stage", + zap.Uint64("from", from), + zap.Uint64("to", to), + ) + s := stagedsync.PruneState{ID: stages.Execution, ForwardProgress: to} + num := to - from + cfg := stagedsync.StageExecuteBlocksCfg(db.rw, + prune.Mode{ + History: prune.Distance(num), + Receipts: prune.Distance(num), + CallTraces: prune.Distance(num), + }, + 0, nil, nil, nil, nil, nil, false, false, false, datadir.Dirs{}, nil, nil, nil, ethconfig.Sync{}, nil, nil) + err := stagedsync.PruneExecutionStage(&s, tx, cfg, ctx, false) + if err != nil { + return errors.Wrapf(err, "failed to prune execution stage from %d to %d", from, to) + } + log.L().Debug("erigon working set store prune execution stage done", + zap.Uint64("from", from), + zap.Uint64("to", to), + zap.Uint64("progress", s.PruneProgress), + ) + if to%5000 == 0 { + log.L().Info("prune execution stage", zap.Uint64("from", from), zap.Uint64("to", to), zap.Uint64("progress", s.PruneProgress)) + } + return nil +} + +// Start starts the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Start(ctx context.Context) error { + return nil +} + +// Stop stops the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Stop(ctx context.Context) error { + return nil +} + +// FinalizeTx finalizes the transaction +func (store *ErigonWorkingSetStore) FinalizeTx(ctx context.Context) error { + blkCtx := protocol.MustGetBlockCtx(ctx) + g := genesis.MustExtractGenesisContext(ctx) + chainCfg, err := evm.NewChainConfig(ctx) + if err != nil { + return err + } + chainRules := chainCfg.Rules(new(big.Int).SetUint64(blkCtx.BlockHeight), g.IsSumatra(blkCtx.BlockHeight), uint64(blkCtx.BlockTimeStamp.Unix())) + rules := evm.NewErigonRules(&chainRules) + return store.backend.intraBlockState.FinalizeTx(rules, erigonstate.NewNoopWriter()) +} + +// Finalize finalizes the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Finalize(ctx context.Context) error { + return nil +} + +func (store *ErigonWorkingSetStore) prepareCommit(ctx context.Context, tx kv.RwTx, retention uint64) error { + blkCtx := protocol.MustGetBlockCtx(ctx) + height := blkCtx.BlockHeight + ts := blkCtx.BlockTimeStamp.Unix() + g := genesis.MustExtractGenesisContext(ctx) + chainCfg, err := evm.NewChainConfig(ctx) + if err != nil { + return err + } + + chainRules := chainCfg.Rules(big.NewInt(int64(height)), g.IsSumatra(height), uint64(ts)) + rules := evm.NewErigonRules(&chainRules) + tsw := erigonstate.NewPlainStateWriter(tx, tx, height) + log.L().Debug("intraBlockState Commit block", zap.Uint64("height", height)) + err = store.backend.intraBlockState.CommitBlock(rules, tsw) + if err != nil { + return err + } + log.L().Debug("erigon store finalize", zap.Uint64("height", height), zap.String("tsw", fmt.Sprintf("%T", tsw))) + // store.intraBlockState.Print(*rules) + + log.L().Debug("erigon store write changesets", zap.Uint64("height", height)) + err = tsw.WriteChangeSets() + if err != nil { + return err + } + err = tsw.WriteHistory() + if err != nil { + return err + } + log.L().Debug("erigon store commit tx", zap.Uint64("height", height)) + err = tx.Put(systemNS, heightKey, uint256.NewInt(height).Bytes()) + if err != nil { + return err + } + // Prune store if retention is set + if retention == 0 || retention >= blkCtx.BlockHeight { + return nil + } + from, to := blkCtx.BlockHeight-retention, blkCtx.BlockHeight + return store.db.Prune(ctx, tx, from, to) +} + +// Commit commits the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Commit(ctx context.Context, retention uint64) error { + defer store.tx.Rollback() + // BeginRw accounting for the context Done signal + // statedb has been committed, so we should not use the context + tx, err := store.db.rw.BeginRw(context.Background()) + if err != nil { + return errors.Wrap(err, "failed to begin erigon working set store transaction") + } + defer tx.Rollback() + + if err = store.prepareCommit(ctx, tx, retention); err != nil { + return errors.Wrap(err, "failed to prepare erigon working set store commit") + } + if err = tx.Commit(); err != nil { + return errors.Wrap(err, "failed to commit erigon working set store transaction") + } + return nil +} + +// Close closes the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Close() { + store.tx.Rollback() +} + +// Snapshot creates a snapshot of the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Snapshot() int { + return store.backend.intraBlockState.Snapshot() +} + +// RevertSnapshot reverts the ErigonWorkingSetStore to a snapshot +func (store *ErigonWorkingSetStore) RevertSnapshot(sn int) error { + store.backend.intraBlockState.RevertToSnapshot(sn) + return nil +} + +// ResetSnapshots resets the snapshots of the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) ResetSnapshots() {} + +// PutObject puts an object into the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) PutObject(ns string, key []byte, obj any) (err error) { + storage, err := store.NewObjectStorage(ns, obj) + if err != nil { + return err + } + if storage == nil { + // TODO: return error after all types are supported + return 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.Store(key, obj) +} + +// GetObject gets an object from the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) GetObject(ns string, key []byte, obj any) error { + storage, err := store.NewObjectStorage(ns, obj) + if err != nil { + return err + } + if storage == nil { + // TODO: return error after all types are supported + return 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)) + return storage.Load(key, obj) +} + +// DeleteObject deletes an object from the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) DeleteObject(ns string, key []byte, obj any) error { + storage, err := store.NewObjectStorage(ns, obj) + if err != nil { + return err + } + if storage == nil { + // TODO: return error after all types are supported + return nil + } + log.L().Debug("delete object", zap.String("namespace", ns), log.Hex("key", key), zap.String("type", fmt.Sprintf("%T", obj))) + return storage.Delete(key) +} + +// 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) + if err != nil { + return nil, err + } + if storage == nil { + return nil, errors.Errorf("unsupported object type %T in ns %s", obj, ns) + } + if len(keys) == 0 { + return storage.List() + } + return storage.Batch(keys) +} + +// Digest returns the digest of the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) Digest() hash.Hash256 { + return hash.ZeroHash256 +} + +// CreateGenesisStates creates the genesis states in the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) CreateGenesisStates(_ context.Context) error { + deployer := store.backend + 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 +} + +// Height returns the current height of the ErigonDB +func (db *ErigonDB) Height() (uint64, error) { + var height uint64 + err := db.rw.View(context.Background(), func(tx kv.Tx) error { + heightBytes, err := tx.GetOne(systemNS, heightKey) + if err != nil { + return errors.Wrap(err, "failed to get height from erigon working set store") + } + if len(heightBytes) == 0 { + return nil // height not set yet + } + height256 := new(uint256.Int) + height256.SetBytes(heightBytes) + height = height256.Uint64() + return nil + }) + if err != nil { + return 0, errors.Wrap(err, "failed to get height from erigon working set store") + } + return height, nil +} + +func newContractBackend(ctx context.Context, intraBlockState *erigonstate.IntraBlockState, sr erigonstate.StateReader) *contractBackend { + 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(intraBlockState, sr, blkCtx.BlockHeight, blkCtx.BlockTimeStamp, blkCtx.Producer, &g, bcCtx.EvmNetworkID, protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount) +} + +// KVStore returns nil as ErigonWorkingSetStore does not implement KVStore +func (store *ErigonWorkingSetStore) KVStore() db.KVStore { + return nil +} + +// IntraBlockState returns the intraBlockState of the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) IntraBlockState() *erigonstate.IntraBlockState { + return store.backend.intraBlockState +} + +// StateReader returns the state reader of the ErigonWorkingSetStore +func (store *ErigonWorkingSetStore) StateReader() erigonstate.StateReader { + return store.backend.org +} + +func (store *ErigonWorkingSetStore) NewObjectStorage(ns string, obj any) (ObjectStorage, error) { + cs, err := storageRegistry.ObjectStorage(ns, obj, store.backend) + switch errors.Cause(err) { + case nil: + return cs, nil + case ErrObjectStorageNotRegistered: + // TODO: fail unknown namespace + return nil, nil + default: + return nil, err + } +} 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/factory/statedb.go b/state/factory/statedb.go index de98e315fe..a3f6915d40 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -32,6 +32,7 @@ import ( "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/prometheustimer" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" ) type ( @@ -54,7 +55,7 @@ type ( protocolViews *protocol.Views skipBlockValidationOnPut bool ps *patchStore - erigonDB *erigonDB + erigonDB *erigonstore.ErigonDB } ) @@ -120,7 +121,7 @@ func NewStateDB(cfg Config, dao db.KVStore, opts ...StateDBOption) (Factory, err } sdb.timerFactory = timerFactory if len(cfg.Chain.HistoryIndexPath) > 0 { - sdb.erigonDB = newErigonDB(cfg.Chain.HistoryIndexPath) + sdb.erigonDB = erigonstore.NewErigonDB(cfg.Chain.HistoryIndexPath) } return &sdb, nil @@ -225,7 +226,7 @@ func (sdb *stateDB) newWorkingSet(ctx context.Context, height uint64) (*workingS if sdb.erigonDB == nil { return ws, nil } - e, err := sdb.erigonDB.newErigonStore(ctx, height) + e, err := sdb.erigonDB.NewErigonStore(ctx, height) if err != nil { return nil, err } @@ -366,7 +367,7 @@ func (sdb *stateDB) WorkingSetAtTransaction(ctx context.Context, height uint64, return nil, err } if sdb.erigonDB != nil { - e, err := sdb.erigonDB.newErigonStoreDryrun(ctx, height) + e, err := sdb.erigonDB.NewErigonStoreDryrun(ctx, height) if err != nil { return nil, err } @@ -406,7 +407,7 @@ func (sdb *stateDB) WorkingSetAtHeight(ctx context.Context, height uint64) (prot ) } } - e, err := sdb.erigonDB.newErigonStoreDryrun(ctx, height+1) + e, err := sdb.erigonDB.NewErigonStoreDryrun(ctx, height+1) if err != nil { return nil, err } diff --git a/state/factory/workingset.go b/state/factory/workingset.go index b0c18663c1..50be49e1f1 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -34,6 +34,7 @@ import ( "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" ) var ( @@ -352,11 +353,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) { @@ -367,14 +364,11 @@ 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) - if err != nil { - return 0, nil, err - } - iter, err := state.NewIterator(keys, values) + iter, err := ws.store.States(cfg.Namespace, cfg.Object, cfg.Keys) if err != nil { return 0, nil, err } + return ws.height, iter, nil } @@ -385,11 +379,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 @@ -399,7 +389,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 @@ -415,6 +405,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 { @@ -1030,7 +1023,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 } @@ -1045,12 +1042,12 @@ func (ws *workingSet) Close() { func (ws *workingSet) Erigon() (*erigonstate.IntraBlockState, bool) { switch st := ws.store.(type) { case *workingSetStoreWithSecondary: - if wss, ok := st.writerSecondary.(*erigonWorkingSetStore); ok { - return wss.intraBlockState, false + if wss, ok := st.writerSecondary.(*erigonstore.ErigonWorkingSetStore); ok { + return wss.IntraBlockState(), false } return nil, false case *erigonWorkingSetStoreForSimulate: - return st.erigonStore.intraBlockState, true + return st.erigonStore.IntraBlockState(), true default: return nil, false } diff --git a/state/factory/workingsetstore.go b/state/factory/workingsetstore.go index 7c2651c781..cf5919fcf6 100644 --- a/state/factory/workingsetstore.go +++ b/state/factory/workingsetstore.go @@ -21,9 +21,14 @@ 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 + States(ns string, object any, keys [][]byte) (state.Iterator, 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 +36,7 @@ type ( RevertSnapshot(int) error ResetSnapshots() Close() + CreateGenesisStates(context.Context) error } stateDBWorkingSetStore struct { @@ -65,9 +71,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 { if err := store.flusher.KVStoreWithBuffer().Put(ns, key, value); err != nil { return errors.Wrap(err, "failed to put value") } @@ -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,12 +174,19 @@ 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, obj any, keys [][]byte) (state.Iterator, error) { + var values [][]byte + var err 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) + keys, values, err = readStates(store.flusher.KVStoreWithBuffer(), ns, keys) + } else { + keys, values, err = readStates(store.flusher.BaseKVStore(), ns, keys) + } + if err != nil { + return nil, err } - return readStates(store.flusher.BaseKVStore(), ns, keys) + return state.NewIterator(keys, values) } func (store *stateDBWorkingSetStore) Finalize(ctx context.Context) error { @@ -162,3 +205,11 @@ func (store *stateDBWorkingSetStore) FinalizeTx(_ context.Context) error { } 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 deleted file mode 100644 index 9ff9c86689..0000000000 --- a/state/factory/workingsetstore_erigon.go +++ /dev/null @@ -1,371 +0,0 @@ -package factory - -import ( - "context" - "fmt" - "math/big" - - libcommon "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon-lib/common/datadir" - "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon-lib/kv/mdbx" - "github.com/erigontech/erigon-lib/kv/temporal/historyv2" - erigonlog "github.com/erigontech/erigon-lib/log/v3" - erigonstate "github.com/erigontech/erigon/core/state" - "github.com/erigontech/erigon/eth/ethconfig" - "github.com/erigontech/erigon/eth/stagedsync" - "github.com/erigontech/erigon/eth/stagedsync/stages" - "github.com/erigontech/erigon/ethdb/prune" - "github.com/holiman/uint256" - "github.com/pkg/errors" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - - "github.com/iotexproject/go-pkgs/hash" - - "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/action/protocol/account/accountpb" - "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" - "github.com/iotexproject/iotex-core/v2/blockchain/genesis" - "github.com/iotexproject/iotex-core/v2/db" - "github.com/iotexproject/iotex-core/v2/db/batch" - "github.com/iotexproject/iotex-core/v2/pkg/log" - "github.com/iotexproject/iotex-core/v2/state" -) - -const ( - systemNS = "erigonsystem" -) - -var ( - heightKey = []byte("height") -) - -type erigonDB struct { - path string - rw kv.RwDB -} - -type erigonWorkingSetStore struct { - db *erigonDB - intraBlockState *erigonstate.IntraBlockState - tx kv.Tx -} - -func newErigonDB(path string) *erigonDB { - return &erigonDB{path: path} -} - -func (db *erigonDB) Start(ctx context.Context) error { - log.L().Info("starting history state index") - lg := erigonlog.New() - lg.SetHandler(erigonlog.StdoutHandler) - rw, err := mdbx.NewMDBX(lg).Path(db.path).WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { - defaultBuckets[systemNS] = kv.TableCfgItem{} - return defaultBuckets - }).Open(ctx) - if err != nil { - return errors.Wrap(err, "failed to open history state index") - } - db.rw = rw - return nil -} - -func (db *erigonDB) Stop(ctx context.Context) { - if db.rw != nil { - db.rw.Close() - } -} - -func (db *erigonDB) newErigonStore(ctx context.Context, height uint64) (*erigonWorkingSetStore, error) { - tx, err := db.rw.BeginRo(ctx) - if err != nil { - return nil, err - } - r := erigonstate.NewPlainStateReader(tx) - intraBlockState := erigonstate.New(r) - return &erigonWorkingSetStore{ - db: db, - tx: tx, - intraBlockState: intraBlockState, - }, nil -} - -func (db *erigonDB) newErigonStoreDryrun(ctx context.Context, height uint64) (*erigonWorkingSetStore, error) { - tx, err := db.rw.BeginRo(ctx) - if err != nil { - return nil, err - } - tsw := erigonstate.NewPlainState(tx, height, nil) - intraBlockState := erigonstate.New(tsw) - return &erigonWorkingSetStore{ - db: db, - tx: tx, - intraBlockState: intraBlockState, - }, nil -} - -func (db *erigonDB) BatchPrune(ctx context.Context, from, to, batch uint64) error { - if from >= to { - return errors.Errorf("invalid prune range: from %d >= to %d", from, to) - } - tx, err := db.rw.BeginRo(ctx) - if err != nil { - return errors.Wrap(err, "failed to begin erigon working set store transaction") - } - base, err := historyv2.AvailableFrom(tx) - if err != nil { - tx.Rollback() - return errors.Wrap(err, "failed to get available from erigon working set store") - } - tx.Rollback() - - if base >= from { - log.L().Debug("batch prune nothing", zap.Uint64("from", from), zap.Uint64("to", to), zap.Uint64("base", base)) - // nothing to prune - return nil - } - - for batchFrom := base; batchFrom < from; batchFrom += batch { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - log.L().Info("batch prune", zap.Uint64("from", batchFrom), zap.Uint64("to", to)) - if err = db.Prune(ctx, nil, batchFrom, to); err != nil { - return err - } - } - log.L().Info("batch prune", zap.Uint64("from", from), zap.Uint64("to", to)) - return db.Prune(ctx, nil, from, to) -} - -func (db *erigonDB) Prune(ctx context.Context, tx kv.RwTx, from, to uint64) error { - if from >= to { - return errors.Errorf("invalid prune range: from %d >= to %d", from, to) - } - log.L().Debug("erigon working set store prune execution stage", - zap.Uint64("from", from), - zap.Uint64("to", to), - ) - s := stagedsync.PruneState{ID: stages.Execution, ForwardProgress: to} - num := to - from - cfg := stagedsync.StageExecuteBlocksCfg(db.rw, - prune.Mode{ - History: prune.Distance(num), - Receipts: prune.Distance(num), - CallTraces: prune.Distance(num), - }, - 0, nil, nil, nil, nil, nil, false, false, false, datadir.Dirs{}, nil, nil, nil, ethconfig.Sync{}, nil, nil) - err := stagedsync.PruneExecutionStage(&s, tx, cfg, ctx, false) - if err != nil { - return errors.Wrapf(err, "failed to prune execution stage from %d to %d", from, to) - } - log.L().Debug("erigon working set store prune execution stage done", - zap.Uint64("from", from), - zap.Uint64("to", to), - zap.Uint64("progress", s.PruneProgress), - ) - if to%5000 == 0 { - log.L().Info("prune execution stage", zap.Uint64("from", from), zap.Uint64("to", to), zap.Uint64("progress", s.PruneProgress)) - } - return nil -} - -func (store *erigonWorkingSetStore) Start(ctx context.Context) error { - return nil -} - -func (store *erigonWorkingSetStore) Stop(ctx context.Context) error { - return nil -} - -func (store *erigonWorkingSetStore) FinalizeTx(ctx context.Context) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - g := genesis.MustExtractGenesisContext(ctx) - chainCfg, err := evm.NewChainConfig(ctx) - if err != nil { - return err - } - chainRules := chainCfg.Rules(new(big.Int).SetUint64(blkCtx.BlockHeight), g.IsSumatra(blkCtx.BlockHeight), uint64(blkCtx.BlockTimeStamp.Unix())) - rules := evm.NewErigonRules(&chainRules) - return store.intraBlockState.FinalizeTx(rules, erigonstate.NewNoopWriter()) -} - -func (store *erigonWorkingSetStore) Finalize(ctx context.Context) error { - return nil -} - -func (store *erigonWorkingSetStore) prepareCommit(ctx context.Context, tx kv.RwTx, retention uint64) error { - blkCtx := protocol.MustGetBlockCtx(ctx) - height := blkCtx.BlockHeight - ts := blkCtx.BlockTimeStamp.Unix() - g := genesis.MustExtractGenesisContext(ctx) - chainCfg, err := evm.NewChainConfig(ctx) - if err != nil { - return err - } - - chainRules := chainCfg.Rules(big.NewInt(int64(height)), g.IsSumatra(height), uint64(ts)) - rules := evm.NewErigonRules(&chainRules) - tsw := erigonstate.NewPlainStateWriter(tx, tx, height) - log.L().Debug("intraBlockState Commit block", zap.Uint64("height", height)) - err = store.intraBlockState.CommitBlock(rules, tsw) - if err != nil { - return err - } - log.L().Debug("erigon store finalize", zap.Uint64("height", height), zap.String("tsw", fmt.Sprintf("%T", tsw))) - // store.intraBlockState.Print(*rules) - - log.L().Debug("erigon store write changesets", zap.Uint64("height", height)) - err = tsw.WriteChangeSets() - if err != nil { - return err - } - err = tsw.WriteHistory() - if err != nil { - return err - } - log.L().Debug("erigon store commit tx", zap.Uint64("height", height)) - err = tx.Put(systemNS, heightKey, uint256.NewInt(height).Bytes()) - if err != nil { - return err - } - // Prune store if retention is set - if retention == 0 || retention >= blkCtx.BlockHeight { - return nil - } - from, to := blkCtx.BlockHeight-retention, blkCtx.BlockHeight - return store.db.Prune(ctx, tx, from, to) -} - -func (store *erigonWorkingSetStore) Commit(ctx context.Context, retention uint64) error { - defer store.tx.Rollback() - // BeginRw accounting for the context Done signal - // statedb has been committed, so we should not use the context - tx, err := store.db.rw.BeginRw(context.Background()) - if err != nil { - return errors.Wrap(err, "failed to begin erigon working set store transaction") - } - defer tx.Rollback() - - if err = store.prepareCommit(ctx, tx, retention); err != nil { - return errors.Wrap(err, "failed to prepare erigon working set store commit") - } - if err = tx.Commit(); err != nil { - return errors.Wrap(err, "failed to commit erigon working set store transaction") - } - return nil -} - -func (store *erigonWorkingSetStore) Close() { - store.tx.Rollback() -} - -func (store *erigonWorkingSetStore) Snapshot() int { - return store.intraBlockState.Snapshot() -} - -func (store *erigonWorkingSetStore) RevertSnapshot(sn int) error { - store.intraBlockState.RevertToSnapshot(sn) - return nil -} - -func (store *erigonWorkingSetStore) ResetSnapshots() {} - -func (store *erigonWorkingSetStore) Put(ns string, key []byte, value []byte) (err error) { - // only handling account, contract storage handled by evm adapter - // others are ignored - if ns != AccountKVNamespace { - return nil - } - defer func() { - if r := recover(); r != nil { - log.L().Warn("store no account in account namespace", zap.Any("recover", r), log.Hex("key", key), zap.String("ns", ns), zap.ByteString("value", value)) - err = nil - } - }() - acc := &state.Account{} - if err := acc.Deserialize(value); err != nil { - // should be legacy rewarding funds - log.L().Warn("store no account in account namespace", log.Hex("key", key), zap.String("ns", ns), zap.ByteString("value", value)) - return nil - } - addr := libcommon.Address(key) - if !store.intraBlockState.Exist(addr) { - store.intraBlockState.CreateAccount(addr, false) - } - store.intraBlockState.SetBalance(addr, uint256.MustFromBig(acc.Balance)) - store.intraBlockState.SetNonce(addr, acc.PendingNonce()) // TODO(erigon): not sure if this is correct - return nil -} - -func (store *erigonWorkingSetStore) Get(ns string, key []byte) ([]byte, error) { - switch ns { - case AccountKVNamespace: - accProto := &accountpb.Account{} - addr := libcommon.Address(key) - if !store.intraBlockState.Exist(addr) { - return nil, state.ErrStateNotExist - } - balance := store.intraBlockState.GetBalance(addr) - accProto.Balance = balance.String() - nonce := store.intraBlockState.GetNonce(addr) - accProto.Nonce = nonce - accProto.Type = accountpb.AccountType_ZERO_NONCE - if ch := store.intraBlockState.GetCodeHash(addr); len(ch) > 0 { - accProto.CodeHash = store.intraBlockState.GetCodeHash(addr).Bytes() - } - return proto.Marshal(accProto) - case evm.CodeKVNameSpace: - addr := libcommon.Address(key) - if !store.intraBlockState.Exist(addr) { - return nil, state.ErrStateNotExist - } - return store.intraBlockState.GetCode(addr), nil - default: - return nil, errors.Errorf("unexpected erigon get namespace %s, key %x", ns, key) - } -} - -func (store *erigonWorkingSetStore) Delete(ns string, key []byte) error { - return nil -} - -func (store *erigonWorkingSetStore) WriteBatch(batch.KVStoreBatch) error { - return nil -} - -func (store *erigonWorkingSetStore) Filter(string, db.Condition, []byte, []byte) ([][]byte, [][]byte, error) { - return nil, nil, nil -} - -func (store *erigonWorkingSetStore) States(string, [][]byte) ([][]byte, [][]byte, error) { - return nil, nil, nil -} - -func (store *erigonWorkingSetStore) Digest() hash.Hash256 { - return hash.ZeroHash256 -} - -func (store *erigonDB) Height() (uint64, error) { - var height uint64 - err := store.rw.View(context.Background(), func(tx kv.Tx) error { - heightBytes, err := tx.GetOne(systemNS, heightKey) - if err != nil { - return errors.Wrap(err, "failed to get height from erigon working set store") - } - if len(heightBytes) == 0 { - return nil // height not set yet - } - height256 := new(uint256.Int) - height256.SetBytes(heightBytes) - height = height256.Uint64() - return nil - }) - if err != nil { - return 0, errors.Wrap(err, "failed to get height from erigon working set store") - } - return height, nil -} diff --git a/state/factory/workingsetstore_erigon_simulate.go b/state/factory/workingsetstore_erigon_simulate.go index 81f276e437..d31d604275 100644 --- a/state/factory/workingsetstore_erigon_simulate.go +++ b/state/factory/workingsetstore_erigon_simulate.go @@ -5,8 +5,9 @@ import ( "github.com/iotexproject/go-pkgs/hash" - "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" "github.com/iotexproject/iotex-core/v2/db" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/state/factory/erigonstore" ) // erigonWorkingSetStoreForSimulate is a working set store that uses erigon as the main store @@ -15,10 +16,10 @@ import ( type erigonWorkingSetStoreForSimulate struct { writer store workingSetStore // fallback to statedb for staking, rewarding and poll - erigonStore *erigonWorkingSetStore + erigonStore *erigonstore.ErigonWorkingSetStore } -func newErigonWorkingSetStoreForSimulate(store workingSetStore, erigonStore *erigonWorkingSetStore) *erigonWorkingSetStoreForSimulate { +func newErigonWorkingSetStoreForSimulate(store workingSetStore, erigonStore *erigonstore.ErigonWorkingSetStore) *erigonWorkingSetStoreForSimulate { return &erigonWorkingSetStoreForSimulate{ store: store, erigonStore: erigonStore, @@ -34,18 +35,27 @@ func (store *erigonWorkingSetStoreForSimulate) Stop(context.Context) error { return nil } -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) GetObject(ns string, key []byte, obj any) error { + storage, err := store.erigonStore.NewObjectStorage(ns, obj) + if err != nil { + return err } + if storage != nil { + return store.erigonStore.GetObject(ns, key, obj) + } + return store.store.GetObject(ns, key, obj) } -func (store *erigonWorkingSetStoreForSimulate) States(ns string, keys [][]byte) ([][]byte, [][]byte, error) { +func (store *erigonWorkingSetStoreForSimulate) States(ns string, obj any, keys [][]byte) (state.Iterator, error) { + storage, err := store.erigonStore.NewObjectStorage(ns, obj) + if err != nil { + return nil, err + } + if storage != nil { + return store.erigonStore.States(ns, obj, keys) + } // currently only used for staking & poll, no need to read from erigon - return store.store.States(ns, keys) + return store.store.States(ns, obj, keys) } func (store *erigonWorkingSetStoreForSimulate) Finalize(_ context.Context) error { @@ -56,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() } @@ -72,3 +78,11 @@ 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 +} + +func (store *erigonWorkingSetStoreForSimulate) KVStore() db.KVStore { + return nil +} diff --git a/state/factory/workingsetstore_test.go b/state/factory/workingsetstore_test.go index dab2f99d5c..de104e6e5b 100644 --- a/state/factory/workingsetstore_test.go +++ b/state/factory/workingsetstore_test.go @@ -11,14 +11,27 @@ import ( "encoding/hex" "testing" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/state" ) +type valueBytes []byte + +func (v *valueBytes) Serialize() ([]byte, error) { + return *v, nil +} + +func (v *valueBytes) Deserialize(data []byte) error { + *v = data + return nil +} + func TestStateDBWorkingSetStore(t *testing.T) { require := require.New(t) ctx := context.Background() @@ -30,31 +43,43 @@ func TestStateDBWorkingSetStore(t *testing.T) { require.NoError(store.Start(ctx)) namespace := "namespace" key1 := []byte("key1") - value1 := []byte("value1") + value1 := valueBytes("value1") key2 := []byte("key2") - value2 := []byte("value2") + value2 := valueBytes("value2") key3 := []byte("key3") - value3 := []byte("value3") + value3 := valueBytes("value3") t.Run("test kvstore feature", func(t *testing.T) { - _, err := store.Get(namespace, key1) + var value valueBytes + 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 valueBytes + 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}) - require.Equal(3, len(valuesInStore)) + iter, err := store.States(namespace, nil, [][]byte{key1, key2, key3}) + require.NoError(err) + require.Equal(3, iter.Size()) + var valuesInStore []valueBytes + for { + vb := valueBytes{} + if _, err := iter.Next(&vb); err == nil { + valuesInStore = append(valuesInStore, vb) + } else { + break + } + } require.True(bytes.Equal(value1, valuesInStore[0])) require.True(bytes.Equal(value2, valuesInStore[1])) require.True(bytes.Equal(value3, valuesInStore[2])) @@ -63,26 +88,41 @@ 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}) - require.Equal(3, len(valuesInStore)) + iter, err = store.States(namespace, &valueInStore, [][]byte{key1, key2, key3}) + require.NoError(err) + require.Equal(3, iter.Size()) + valuesInStore = []valueBytes{} + for { + vb := valueBytes{} + switch _, err := iter.Next(&vb); errors.Cause(err) { + case state.ErrNilValue: + valuesInStore = append(valuesInStore, nil) + continue + case nil: + valuesInStore = append(valuesInStore, vb) + continue + } + break + } 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 valueBytes + err := store.GetObject(AccountKVNamespace, []byte(CurrentHeightKey), &value) require.Error(err) _, err = inMemStore.Get(AccountKVNamespace, []byte(CurrentHeightKey)) require.Error(err) @@ -90,7 +130,8 @@ func TestStateDBWorkingSetStore(t *testing.T) { BlockHeight: height, }) require.NoError(store.Finalize(ctx)) - heightInStore, err := store.Get(AccountKVNamespace, []byte(CurrentHeightKey)) + var heightInStore valueBytes + 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 c4c28130d4..7ca49dc934 100644 --- a/state/factory/workingsetstore_with_secondary.go +++ b/state/factory/workingsetstore_with_secondary.go @@ -8,8 +8,8 @@ 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" + "github.com/iotexproject/iotex-core/v2/state" ) var ( @@ -23,19 +23,20 @@ var ( ) type reader interface { - Get(string, []byte) ([]byte, error) - States(string, [][]byte) ([][]byte, [][]byte, error) + // Get(string, []byte) ([]byte, error) + GetObject(string, []byte, any) error + States(string, any, [][]byte) (state.Iterator, 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 - Delete(ns string, key []byte) error + PutObject(ns string, key []byte, obj any) 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 @@ -78,25 +79,18 @@ 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 { +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.WriteBatch(batch) + return store.writerSecondary.PutObject(ns, key, obj) } -func (store *workingSetStoreWithSecondary) Put(ns string, key []byte, value []byte) error { - if err := store.writer.Put(ns, key, value); err != nil { +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.Put(ns, key, value) -} - -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) + return store.writerSecondary.DeleteObject(ns, key, obj) } func (store *workingSetStoreWithSecondary) Commit(ctx context.Context, retention uint64) error { @@ -146,3 +140,22 @@ 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, obj any, keys [][]byte) (state.Iterator, error) { + return store.reader.States(ns, obj, keys) +} + +func (store *workingSetStoreWithSecondary) KVStore() db.KVStore { + return nil +} 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" +) diff --git a/systemcontracts/GenericStorage.sol b/systemcontracts/GenericStorage.sol new file mode 100644 index 0000000000..290afd0a55 --- /dev/null +++ b/systemcontracts/GenericStorage.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @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 + * - Owner-only access control for modification operations + */ +contract GenericStorage is Ownable { + + /** + * @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 + } + + // Array to keep track of all keys for listing functionality + bytes[] private keys_; + + // Array to store values corresponding to keys (parallel arrays) + GenericValue[] private values_; + + // Mapping to store the index of each key in the keys_ array for O(1) removal + // Note: We store (actualIndex + 1) to distinguish between non-existent keys (0) and keys at index 0 (1) + mapping(bytes => uint256) private keyIndex_; + + // Events + event DataStored(bytes indexed key); + event DataDeleted(bytes indexed key); + event BatchDataRetrieved(uint256 keyCount); + event StorageCleared(); + + /** + * @dev Constructor that sets the deployer as the owner + */ + constructor() Ownable(msg.sender) {} + + /** + * @dev Internal function to check if a key exists + * @param key The storage key to check + * @return Whether the key exists + */ + function _keyExists(bytes memory key) private view returns (bool) { + return keyIndex_[key] != 0; + } + + /** + * @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 onlyOwner { + require(key.length > 0, "Key cannot be empty"); + + // If key doesn't exist, add it to keys array + if (!_keyExists(key)) { + keyIndex_[key] = keys_.length + 1; // Store (index + 1) to distinguish from non-existent keys + keys_.push(key); + values_.push(value); // Add corresponding value to values array + } else { + // Update existing value + values_[keyIndex_[key] - 1] = value; + } + + emit DataStored(key); + } + + /** + * @dev Delete data by key + * @param key The storage key to delete + */ + function remove(bytes memory key) external onlyOwner { + require(_keyExists(key), "Key does not exist"); + + // Get the index of the key to remove (subtract 1 since we stored index + 1) + uint256 indexToRemove = keyIndex_[key] - 1; + uint256 lastIndex = keys_.length - 1; + + // If it's not the last element, move the last element to the removed position + if (indexToRemove != lastIndex) { + keys_[indexToRemove] = keys_[lastIndex]; + values_[indexToRemove] = values_[lastIndex]; + keyIndex_[keys_[lastIndex]] = indexToRemove + 1; // Update the moved key's index (add 1) + } + + // Remove the last elements from both arrays + keys_.pop(); + values_.pop(); + delete keyIndex_[key]; + + 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 = values_[keyIndex_[key] - 1]; // Get value from values array + } + 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] = values_[keyIndex_[keyList[i]] - 1]; // Get value from values array + } + } + + 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 - much more efficient with parallel arrays + for (uint256 i = 0; i < actualLimit; i++) { + uint256 arrayIndex = offset + i; + keyList[i] = keys_[arrayIndex]; + values[i] = values_[arrayIndex]; // Direct array access, no mapping lookup needed + } + + 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 onlyOwner { + // Clear all mappings and arrays + for (uint256 i = 0; i < keys_.length; i++) { + bytes memory key = keys_[i]; + delete keyIndex_[key]; + } + + // Clear both arrays + delete keys_; + delete values_; + + emit StorageCleared(); + } +} \ No newline at end of file diff --git a/systemcontracts/NamespaceStorage.sol b/systemcontracts/NamespaceStorage.sol new file mode 100644 index 0000000000..38233e9a6c --- /dev/null +++ b/systemcontracts/NamespaceStorage.sol @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @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 + * - Owner-only access control for modification operations + */ +contract NamespaceStorage is Ownable { + + /** + * @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 + } + + // Track keys for each namespace (parallel arrays) + mapping(string => bytes[]) private namespaceKeys_; + mapping(string => GenericValue[]) private namespaceValues_; + + // Mapping to store the index of each key in the namespace's keys array for O(1) removal + // Note: We store (actualIndex + 1) to distinguish between non-existent keys (0) and keys at index 0 (1) + mapping(string => mapping(bytes => uint256)) private keyIndex_; + + // 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 Constructor that sets the deployer as the owner + */ + constructor() Ownable(msg.sender) {} + + /** + * @dev Internal function to check if a key exists in a namespace + * @param namespace The namespace to check + * @param key The storage key to check + * @return Whether the key exists in the namespace + */ + function _keyExists(string memory namespace, bytes memory key) private view returns (bool) { + return keyIndex_[namespace][key] != 0; + } + + /** + * @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 onlyOwner { + 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 both arrays + if (!_keyExists(namespace, key)) { + keyIndex_[namespace][key] = namespaceKeys_[namespace].length + 1; // Store (index + 1) + namespaceKeys_[namespace].push(key); + namespaceValues_[namespace].push(value); + } else { + // Update existing value + namespaceValues_[namespace][keyIndex_[namespace][key] - 1] = 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 = namespaceValues_[namespace][keyIndex_[namespace][key] - 1]; + } + 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 onlyOwner { + require(namespaceExists_[namespace], "Namespace does not exist"); + require(_keyExists(namespace, key), "Key does not exist in namespace"); + + // Get the index of the key to remove (subtract 1 since we stored index + 1) + uint256 indexToRemove = keyIndex_[namespace][key] - 1; + uint256 lastIndex = namespaceKeys_[namespace].length - 1; + + // If it's not the last element, move the last element to the removed position + if (indexToRemove != lastIndex) { + namespaceKeys_[namespace][indexToRemove] = namespaceKeys_[namespace][lastIndex]; + namespaceValues_[namespace][indexToRemove] = namespaceValues_[namespace][lastIndex]; + keyIndex_[namespace][namespaceKeys_[namespace][lastIndex]] = indexToRemove + 1; // Update the moved key's index (add 1) + } + + // Remove the last elements from both arrays + namespaceKeys_[namespace].pop(); + namespaceValues_[namespace].pop(); + delete keyIndex_[namespace][key]; + + 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] = namespaceValues_[namespace][keyIndex_[namespace][keyList[i]] - 1]; + } + } + + 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 onlyOwner { + 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 both arrays + if (!_keyExists(namespace, keys[i])) { + keyIndex_[namespace][keys[i]] = namespaceKeys_[namespace].length + 1; + namespaceKeys_[namespace].push(keys[i]); + namespaceValues_[namespace].push(values[i]); + } else { + // Update existing value + namespaceValues_[namespace][keyIndex_[namespace][keys[i]] - 1] = 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 - much more efficient with parallel arrays + for (uint256 i = 0; i < actualLimit; i++) { + uint256 arrayIndex = offset + i; + keyList[i] = keys[arrayIndex]; + values[i] = namespaceValues_[namespace][arrayIndex]; // Direct array access, no mapping lookup + } + + 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++) { + namespaceList[i] = namespaces_[offset + i]; + counts[i] = namespaceKeys_[namespaces_[offset + i]].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 onlyOwner { + require(namespaceExists_[namespace], "Namespace does not exist"); + + bytes[] storage keys = namespaceKeys_[namespace]; + + // Clear all key indices in the namespace + for (uint256 i = 0; i < keys.length; i++) { + delete keyIndex_[namespace][keys[i]]; + } + + // Clear both arrays for the namespace + delete namespaceKeys_[namespace]; + delete namespaceValues_[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 onlyOwner { + // Clear all namespaces + for (uint256 i = 0; i < namespaces_.length; i++) { + string memory namespace = namespaces_[i]; + bytes[] storage keys = namespaceKeys_[namespace]; + + // Clear all key indices in this namespace + for (uint256 j = 0; j < keys.length; j++) { + delete keyIndex_[namespace][keys[j]]; + } + + // Clear both arrays for this namespace + delete namespaceKeys_[namespace]; + delete namespaceValues_[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..24d0413e14 --- /dev/null +++ b/systemcontracts/generic_storage.go @@ -0,0 +1,425 @@ +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" +) + +// ContractBackend defines the interface for contract backend operations +type ContractBackend interface { + Call(callMsg *ethereum.CallMsg) ([]byte, error) + Handle(callMsg *ethereum.CallMsg) error +} + +// GenericStorageContract provides an interface to interact with the GenericStorage smart contract +type GenericStorageContract struct { + contractAddress common.Address + backend ContractBackend + abi abi.ABI + owner common.Address +} + +// NewGenericStorageContract creates a new GenericStorage contract instance +func NewGenericStorageContract(contractAddress common.Address, backend ContractBackend, owner common.Address) (*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, + owner: owner, + }, nil +} + +// Address returns the contract address +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{ + From: g.owner, + 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{ + From: common.Address{}, + 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{ + From: g.owner, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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..5b271a8deb --- /dev/null +++ b/systemcontracts/generic_storage_abi.go @@ -0,0 +1,380 @@ +package systemcontracts + +// GenericStorageABI is the ABI definition for the GenericStorage contract +const GenericStorageABI = `[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "keyCount", + "type": "uint256" + } + ], + "name": "BatchDataRetrieved", + "type": "event" + }, + { + "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": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "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": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "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" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]` + +const GenericStorageByteCodeStr = "60806040523480156200001157600080fd5b5033600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000885760006040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016200007f9190620001a9565b60405180910390fd5b6200009981620000a060201b60201c565b50620001c6565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620001918262000164565b9050919050565b620001a38162000184565b82525050565b6000602082019050620001c0600083018462000198565b92915050565b6129ae80620001d66000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c8063715018a611610071578063715018a61461017c57806379fc09a214610186578063821bef2f146101b65780638da5cb5b146101e7578063d6d7d52514610205578063f2fde38b14610236576100b4565b806306661abd146100b9578063072957b0146100d757806342e1ae2e146100f357806350fd73671461012457806352efea6e1461015657806358edef4c14610160575b600080fd5b6100c1610252565b6040516100ce91906117e7565b60405180910390f35b6100f160048036038101906100ec9190611a1e565b61025f565b005b61010d60048036038101906101089190611b7c565b61046e565b60405161011b929190611e2e565b60405180910390f35b61013e60048036038101906101399190611e91565b6107e1565b60405161014d93929190611f93565b60405180910390f35b61015e610c65565b005b61017a60048036038101906101759190611fd8565b610da3565b005b61018461104e565b005b6101a0600480360381019061019b9190611fd8565b611062565b6040516101ad9190612030565b60405180910390f35b6101d060048036038101906101cb9190611e91565b611074565b6040516101de92919061204b565b60405180910390f35b6101ef611243565b6040516101fc91906120bc565b60405180910390f35b61021f600480360381019061021a9190611fd8565b61126c565b60405161022d929190612135565b60405180910390f35b610250600480360381019061024b9190612191565b61149c565b005b6000600180549050905090565b610267611522565b60008251116102ab576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102a29061221b565b60405180910390fd5b6102b4826115a9565b61039557600180805490506102c9919061226a565b6003836040516102d991906122da565b90815260200160405180910390208190555060018290806001815401808255809150506001900390600052602060002001600090919091909150908161031f91906124fd565b506002819080600181540180825580915050600190039060005260206000209060030201600090919091909150600082015181600001908161036191906124fd565b50602082015181600101908161037791906124fd565b50604082015181600201908161038d91906124fd565b505050610428565b80600260016003856040516103aa91906122da565b9081526020016040518091039020546103c391906125cf565b815481106103d4576103d3612603565b5b906000526020600020906003020160008201518160000190816103f791906124fd565b50602082015181600101908161040d91906124fd565b50604082015181600201908161042391906124fd565b509050505b8160405161043691906122da565b60405180910390207f281be1a469724e78f8c4152bcc9fb70a95646c3e1e2ade9293d8d497c976338a60405160405180910390a25050565b606080825167ffffffffffffffff81111561048c5761048b611831565b5b6040519080825280602002602001820160405280156104c557816020015b6104b26116a0565b8152602001906001900390816104aa5790505b509150825167ffffffffffffffff8111156104e3576104e2611831565b5b6040519080825280602002602001820160405280156105115781602001602082028036833780820191505090505b50905060005b83518110156107db5761054384828151811061053657610535612603565b5b60200260200101516115a9565b82828151811061055657610555612603565b5b60200260200101901515908115158152505081818151811061057b5761057a612603565b5b6020026020010151156107ce576002600160038684815181106105a1576105a0612603565b5b60200260200101516040516105b691906122da565b9081526020016040518091039020546105cf91906125cf565b815481106105e0576105df612603565b5b906000526020600020906003020160405180606001604052908160008201805461060990612320565b80601f016020809104026020016040519081016040528092919081815260200182805461063590612320565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050815260200160018201805461069b90612320565b80601f01602080910402602001604051908101604052809291908181526020018280546106c790612320565b80156107145780601f106106e957610100808354040283529160200191610714565b820191906000526020600020905b8154815290600101906020018083116106f757829003601f168201915b5050505050815260200160028201805461072d90612320565b80601f016020809104026020016040519081016040528092919081815260200182805461075990612320565b80156107a65780601f1061077b576101008083540402835291602001916107a6565b820191906000526020600020905b81548152906001019060200180831161078957829003601f168201915b5050505050815250508382815181106107c2576107c1612603565b5b60200260200101819052505b8080600101915050610517565b50915091565b606080600060018054905090508085106108a257600067ffffffffffffffff8111156108105761080f611831565b5b60405190808252806020026020018201604052801561084357816020015b606081526020019060019003908161082e5790505b509250600067ffffffffffffffff81111561086157610860611831565b5b60405190808252806020026020018201604052801561089a57816020015b6108876116a0565b81526020019060019003908161087f5790505b509150610c5e565b600085826108b091906125cf565b905060008186116108c157856108c3565b815b90508067ffffffffffffffff8111156108df576108de611831565b5b60405190808252806020026020018201604052801561091257816020015b60608152602001906001900390816108fd5790505b5094508067ffffffffffffffff81111561092f5761092e611831565b5b60405190808252806020026020018201604052801561096857816020015b6109556116a0565b81526020019060019003908161094d5790505b50935060005b81811015610c5a5760008189610984919061226a565b90506001818154811061099a57610999612603565b5b9060005260206000200180546109af90612320565b80601f01602080910402602001604051908101604052809291908181526020018280546109db90612320565b8015610a285780601f106109fd57610100808354040283529160200191610a28565b820191906000526020600020905b815481529060010190602001808311610a0b57829003601f168201915b5050505050878381518110610a4057610a3f612603565b5b602002602001018190525060028181548110610a5f57610a5e612603565b5b9060005260206000209060030201604051806060016040529081600082018054610a8890612320565b80601f0160208091040260200160405190810160405280929190818152602001828054610ab490612320565b8015610b015780601f10610ad657610100808354040283529160200191610b01565b820191906000526020600020905b815481529060010190602001808311610ae457829003601f168201915b50505050508152602001600182018054610b1a90612320565b80601f0160208091040260200160405190810160405280929190818152602001828054610b4690612320565b8015610b935780601f10610b6857610100808354040283529160200191610b93565b820191906000526020600020905b815481529060010190602001808311610b7657829003601f168201915b50505050508152602001600282018054610bac90612320565b80601f0160208091040260200160405190810160405280929190818152602001828054610bd890612320565b8015610c255780601f10610bfa57610100808354040283529160200191610c25565b820191906000526020600020905b815481529060010190602001808311610c0857829003601f168201915b505050505081525050868381518110610c4157610c40612603565b5b602002602001018190525050808060010191505061096e565b5050505b9250925092565b610c6d611522565b60005b600180549050811015610d5857600060018281548110610c9357610c92612603565b5b906000526020600020018054610ca890612320565b80601f0160208091040260200160405190810160405280929190818152602001828054610cd490612320565b8015610d215780601f10610cf657610100808354040283529160200191610d21565b820191906000526020600020905b815481529060010190602001808311610d0457829003601f168201915b50505050509050600381604051610d3891906122da565b908152602001604051809103902060009055508080600101915050610c70565b5060016000610d6791906116c1565b60026000610d7591906116e2565b7f6c7e419df39ff46e811e7d979ebfd916b8d4960de06f479dc69cf8417cdb89e760405160405180910390a1565b610dab611522565b610db4816115a9565b610df3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dea9061267e565b60405180910390fd5b60006001600383604051610e0791906122da565b908152602001604051809103902054610e2091906125cf565b9050600060018080549050610e3591906125cf565b9050808214610f5a5760018181548110610e5257610e51612603565b5b9060005260206000200160018381548110610e7057610e6f612603565b5b906000526020600020019081610e8691906126c9565b5060028181548110610e9b57610e9a612603565b5b906000526020600020906003020160028381548110610ebd57610ebc612603565b5b906000526020600020906003020160008201816000019081610edf91906127c7565b5060018201816001019081610ef491906127c7565b5060028201816002019081610f0991906127c7565b50905050600182610f1a919061226a565b600360018381548110610f3057610f2f612603565b5b90600052602060002001604051610f479190612932565b9081526020016040518091039020819055505b6001805480610f6c57610f6b612949565b5b600190038181906000526020600020016000610f889190611706565b90556002805480610f9c57610f9b612949565b5b600190038181906000526020600020906003020160008082016000610fc19190611706565b600182016000610fd19190611706565b600282016000610fe19190611706565b50509055600383604051610ff591906122da565b9081526020016040518091039020600090558260405161101591906122da565b60405180910390207f9248955bf73fd007be0a0d9dc13c36f9e61c3642fbac04d8ac592e0c21d0103760405160405180910390a2505050565b611056611522565b61106060006115d4565b565b600061106d826115a9565b9050919050565b6060600060018054905090508084106110dd57600067ffffffffffffffff8111156110a2576110a1611831565b5b6040519080825280602002602001820160405280156110d557816020015b60608152602001906001900390816110c05790505b50915061123c565b600084826110eb91906125cf565b905060008185116110fc57846110fe565b815b90508067ffffffffffffffff81111561111a57611119611831565b5b60405190808252806020026020018201604052801561114d57816020015b60608152602001906001900390816111385790505b50935060005b818110156112385760018188611169919061226a565b8154811061117a57611179612603565b5b90600052602060002001805461118f90612320565b80601f01602080910402602001604051908101604052809291908181526020018280546111bb90612320565b80156112085780601f106111dd57610100808354040283529160200191611208565b820191906000526020600020905b8154815290600101906020018083116111eb57829003601f168201915b50505050508582815181106112205761121f612603565b5b60200260200101819052508080600101915050611153565b5050505b9250929050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6112746116a0565b600061127f836115a9565b90508015611497576002600160038560405161129b91906122da565b9081526020016040518091039020546112b491906125cf565b815481106112c5576112c4612603565b5b90600052602060002090600302016040518060600160405290816000820180546112ee90612320565b80601f016020809104026020016040519081016040528092919081815260200182805461131a90612320565b80156113675780601f1061133c57610100808354040283529160200191611367565b820191906000526020600020905b81548152906001019060200180831161134a57829003601f168201915b5050505050815260200160018201805461138090612320565b80601f01602080910402602001604051908101604052809291908181526020018280546113ac90612320565b80156113f95780601f106113ce576101008083540402835291602001916113f9565b820191906000526020600020905b8154815290600101906020018083116113dc57829003601f168201915b5050505050815260200160028201805461141290612320565b80601f016020809104026020016040519081016040528092919081815260200182805461143e90612320565b801561148b5780601f106114605761010080835404028352916020019161148b565b820191906000526020600020905b81548152906001019060200180831161146e57829003601f168201915b50505050508152505091505b915091565b6114a4611522565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036115165760006040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260040161150d91906120bc565b60405180910390fd5b61151f816115d4565b50565b61152a611698565b73ffffffffffffffffffffffffffffffffffffffff16611548611243565b73ffffffffffffffffffffffffffffffffffffffff16146115a75761156b611698565b6040517f118cdaa700000000000000000000000000000000000000000000000000000000815260040161159e91906120bc565b60405180910390fd5b565b6000806003836040516115bc91906122da565b90815260200160405180910390205414159050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600033905090565b60405180606001604052806060815260200160608152602001606081525090565b50805460008255906000526020600020908101906116df9190611746565b50565b5080546000825560030290600052602060002090810190611703919061176a565b50565b50805461171290612320565b6000825580601f106117245750611743565b601f01602090049060005260206000209081019061174291906117b1565b5b50565b5b80821115611766576000818161175d9190611706565b50600101611747565b5090565b5b808211156117ad57600080820160006117849190611706565b6001820160006117949190611706565b6002820160006117a49190611706565b5060030161176b565b5090565b5b808211156117ca5760008160009055506001016117b2565b5090565b6000819050919050565b6117e1816117ce565b82525050565b60006020820190506117fc60008301846117d8565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61186982611820565b810181811067ffffffffffffffff8211171561188857611887611831565b5b80604052505050565b600061189b611802565b90506118a78282611860565b919050565b600067ffffffffffffffff8211156118c7576118c6611831565b5b6118d082611820565b9050602081019050919050565b82818337600083830152505050565b60006118ff6118fa846118ac565b611891565b90508281526020810184848401111561191b5761191a61181b565b5b6119268482856118dd565b509392505050565b600082601f83011261194357611942611816565b5b81356119538482602086016118ec565b91505092915050565b600080fd5b600080fd5b60006060828403121561197c5761197b61195c565b5b6119866060611891565b9050600082013567ffffffffffffffff8111156119a6576119a5611961565b5b6119b28482850161192e565b600083015250602082013567ffffffffffffffff8111156119d6576119d5611961565b5b6119e28482850161192e565b602083015250604082013567ffffffffffffffff811115611a0657611a05611961565b5b611a128482850161192e565b60408301525092915050565b60008060408385031215611a3557611a3461180c565b5b600083013567ffffffffffffffff811115611a5357611a52611811565b5b611a5f8582860161192e565b925050602083013567ffffffffffffffff811115611a8057611a7f611811565b5b611a8c85828601611966565b9150509250929050565b600067ffffffffffffffff821115611ab157611ab0611831565b5b602082029050602081019050919050565b600080fd5b6000611ada611ad584611a96565b611891565b90508083825260208201905060208402830185811115611afd57611afc611ac2565b5b835b81811015611b4457803567ffffffffffffffff811115611b2257611b21611816565b5b808601611b2f898261192e565b85526020850194505050602081019050611aff565b5050509392505050565b600082601f830112611b6357611b62611816565b5b8135611b73848260208601611ac7565b91505092915050565b600060208284031215611b9257611b9161180c565b5b600082013567ffffffffffffffff811115611bb057611baf611811565b5b611bbc84828501611b4e565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b83811015611c2b578082015181840152602081019050611c10565b60008484015250505050565b6000611c4282611bf1565b611c4c8185611bfc565b9350611c5c818560208601611c0d565b611c6581611820565b840191505092915050565b60006060830160008301518482036000860152611c8d8282611c37565b91505060208301518482036020860152611ca78282611c37565b91505060408301518482036040860152611cc18282611c37565b9150508091505092915050565b6000611cda8383611c70565b905092915050565b6000602082019050919050565b6000611cfa82611bc5565b611d048185611bd0565b935083602082028501611d1685611be1565b8060005b85811015611d525784840389528151611d338582611cce565b9450611d3e83611ce2565b925060208a01995050600181019050611d1a565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b611da581611d90565b82525050565b6000611db78383611d9c565b60208301905092915050565b6000602082019050919050565b6000611ddb82611d64565b611de58185611d6f565b9350611df083611d80565b8060005b83811015611e21578151611e088882611dab565b9750611e1383611dc3565b925050600181019050611df4565b5085935050505092915050565b60006040820190508181036000830152611e488185611cef565b90508181036020830152611e5c8184611dd0565b90509392505050565b611e6e816117ce565b8114611e7957600080fd5b50565b600081359050611e8b81611e65565b92915050565b60008060408385031215611ea857611ea761180c565b5b6000611eb685828601611e7c565b9250506020611ec785828601611e7c565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000611f098383611c37565b905092915050565b6000602082019050919050565b6000611f2982611ed1565b611f338185611edc565b935083602082028501611f4585611eed565b8060005b85811015611f815784840389528151611f628582611efd565b9450611f6d83611f11565b925060208a01995050600181019050611f49565b50829750879550505050505092915050565b60006060820190508181036000830152611fad8186611f1e565b90508181036020830152611fc18185611cef565b9050611fd060408301846117d8565b949350505050565b600060208284031215611fee57611fed61180c565b5b600082013567ffffffffffffffff81111561200c5761200b611811565b5b6120188482850161192e565b91505092915050565b61202a81611d90565b82525050565b60006020820190506120456000830184612021565b92915050565b600060408201905081810360008301526120658185611f1e565b905061207460208301846117d8565b9392505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006120a68261207b565b9050919050565b6120b68161209b565b82525050565b60006020820190506120d160008301846120ad565b92915050565b600060608301600083015184820360008601526120f48282611c37565b9150506020830151848203602086015261210e8282611c37565b915050604083015184820360408601526121288282611c37565b9150508091505092915050565b6000604082019050818103600083015261214f81856120d7565b905061215e6020830184612021565b9392505050565b61216e8161209b565b811461217957600080fd5b50565b60008135905061218b81612165565b92915050565b6000602082840312156121a7576121a661180c565b5b60006121b58482850161217c565b91505092915050565b600082825260208201905092915050565b7f4b65792063616e6e6f7420626520656d70747900000000000000000000000000600082015250565b60006122056013836121be565b9150612210826121cf565b602082019050919050565b60006020820190508181036000830152612234816121f8565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000612275826117ce565b9150612280836117ce565b92508282019050808211156122985761229761223b565b5b92915050565b600081905092915050565b60006122b482611bf1565b6122be818561229e565b93506122ce818560208601611c0d565b80840191505092915050565b60006122e682846122a9565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061233857607f821691505b60208210810361234b5761234a6122f1565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026123b37fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82612376565b6123bd8683612376565b95508019841693508086168417925050509392505050565b6000819050919050565b60006123fa6123f56123f0846117ce565b6123d5565b6117ce565b9050919050565b6000819050919050565b612414836123df565b61242861242082612401565b848454612383565b825550505050565b600090565b61243d612430565b61244881848461240b565b505050565b5b8181101561246c57612461600082612435565b60018101905061244e565b5050565b601f8211156124b15761248281612351565b61248b84612366565b8101602085101561249a578190505b6124ae6124a685612366565b83018261244d565b50505b505050565b600082821c905092915050565b60006124d4600019846008026124b6565b1980831691505092915050565b60006124ed83836124c3565b9150826002028217905092915050565b61250682611bf1565b67ffffffffffffffff81111561251f5761251e611831565b5b6125298254612320565b612534828285612470565b600060209050601f8311600181146125675760008415612555578287015190505b61255f85826124e1565b8655506125c7565b601f19841661257586612351565b60005b8281101561259d57848901518255600182019150602085019450602081019050612578565b868310156125ba57848901516125b6601f8916826124c3565b8355505b6001600288020188555050505b505050505050565b60006125da826117ce565b91506125e5836117ce565b92508282039050818111156125fd576125fc61223b565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4b657920646f6573206e6f742065786973740000000000000000000000000000600082015250565b60006126686012836121be565b915061267382612632565b602082019050919050565b600060208201905081810360008301526126978161265b565b9050919050565b6000815490506126ad81612320565b9050919050565b60008190508160005260206000209050919050565b8181036126d75750506127af565b6126e08261269e565b67ffffffffffffffff8111156126f9576126f8611831565b5b6127038254612320565b61270e828285612470565b6000601f83116001811461273d576000841561272b578287015490505b61273585826124e1565b8655506127a8565b601f19841661274b876126b4565b965061275686612351565b60005b8281101561277e57848901548255600182019150600185019450602081019050612759565b8683101561279b5784890154612797601f8916826124c3565b8355505b6001600288020188555050505b5050505050505b565b6000815490506127c081612320565b9050919050565b8181036127d55750506128ad565b6127de826127b1565b67ffffffffffffffff8111156127f7576127f6611831565b5b6128018254612320565b61280c828285612470565b6000601f83116001811461283b5760008415612829578287015490505b61283385826124e1565b8655506128a6565b601f19841661284987612351565b965061285486612351565b60005b8281101561287c57848901548255600182019150600185019450602081019050612857565b868310156128995784890154612895601f8916826124c3565b8355505b6001600288020188555050505b5050505050505b565b600081546128bc81612320565b6128c6818661229e565b945060018216600081146128e157600181146128f657612929565b60ff1983168652811515820286019350612929565b6128ff85612351565b60005b8381101561292157815481890152600182019150602081019050612902565b838801955050505b50505092915050565b600061293e82846128af565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea2646970667358221220742757bbdebb67507bfe37b3bab5f5e531aad9896e9022a68c6faf2c49545c3564736f6c63430008180033" diff --git a/systemcontracts/namespace_storage.go b/systemcontracts/namespace_storage.go new file mode 100644 index 0000000000..654a57979b --- /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 = GenericValue + +// NamespaceBatchGetResult represents the result of a batch get operation +type NamespaceBatchGetResult = BatchGetResult + +// NamespaceListResult represents the result of a list operation +type NamespaceListResult = ListResult + +// NamespaceListKeysResult represents the result of a listKeys operation +type NamespaceListKeysResult = ListKeysResult + +// 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 = GetResult + +// NamespaceStorageContract provides an interface to interact with the NamespaceStorage smart contract +type NamespaceStorageContract struct { + contractAddress common.Address + backend ContractBackend + abi abi.ABI + owner common.Address +} + +// NewNamespaceStorageContract creates a new NamespaceStorage contract instance +func NewNamespaceStorageContract(contractAddress common.Address, backend ContractBackend, owner common.Address) (*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, + owner: owner, + }, 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{ + From: ns.owner, + 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{ + From: common.Address{}, + 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{ + From: ns.owner, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: ns.owner, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: common.Address{}, + 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{ + From: ns.owner, + 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{ + From: ns.owner, + 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..ddc2101fcf --- /dev/null +++ b/systemcontracts/namespace_storage_abi.go @@ -0,0 +1,585 @@ +package systemcontracts + +// NamespaceStorageABI is the ABI definition for the NamespaceStorage contract +const NamespaceStorageABI = `[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "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": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "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": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalCount", + "outputs": [ + { + "internalType": "uint256", + "name": "totalItems", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]` + +// NamespaceStorageContractByteCodeStr is the bytecode for the NamespaceStorage contract +const NamespaceStorageContractByteCodeStr = "60806040523480156200001157600080fd5b5033600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000885760006040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016200007f9190620001a9565b60405180910390fd5b6200009981620000a060201b60201c565b50620001c6565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620001918262000164565b9050919050565b620001a38162000184565b82525050565b6000602082019050620001c0600083018462000198565b92915050565b6143f280620001d66000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c8063912f3095116100a2578063c9037d2d11610071578063c9037d2d146102fa578063d1e968651461032a578063ebb689a11461035a578063f2fde38b14610364578063f53504231461038057610116565b8063912f30951461025d578063a2bc7a1c1461028f578063aff62745146102ab578063b40ec02b146102dc57610116565b80634e8bfabb116100e95780634e8bfabb146101b757806353df2b27146101d357806370eafd1814610205578063715018a6146102355780638da5cb5b1461023f57610116565b806304e523691461011b57806312862f7f1461013757806334eafb11146101685780634b6b9a8c14610186575b600080fd5b61013560048036038101906101309190612b01565b61039c565b005b610151600480360381019061014c9190612c5f565b6107fe565b60405161015f929190612f40565b60405180910390f35b610170610bac565b60405161017d9190612f90565b60405180910390f35b6101a0600480360381019061019b9190612fd7565b610c1e565b6040516101ae929190613108565b60405180910390f35b6101d160048036038101906101cc91906132db565b610e0f565b005b6101ed60048036038101906101e89190613382565b6112ed565b6040516101fc93929190613597565b60405180910390f35b61021f600480360381019061021a91906135dc565b6115bd565b60405161022c9190613634565b60405180910390f35b61023d6115f2565b005b610247611606565b6040516102549190613690565b60405180910390f35b61027760048036038101906102729190612fd7565b61162f565b604051610286939291906136ab565b60405180910390f35b6102a960048036038101906102a491906135dc565b611af2565b005b6102c560048036038101906102c09190612b01565b611c98565b6040516102d392919061374e565b60405180910390f35b6102e4611f03565b6040516102f19190612f90565b60405180910390f35b610314600480360381019061030f91906135dc565b611f10565b6040516103219190612f90565b60405180910390f35b610344600480360381019061033f9190612b01565b611f3b565b6040516103519190613634565b60405180910390f35b610362611f4f565b005b61037e600480360381019061037991906137aa565b612182565b005b61039a600480360381019061039591906137d7565b612208565b005b6103a46125b8565b6005826040516103b491906138ba565b908152602001604051809103902060009054906101000a900460ff1661040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104069061392e565b60405180910390fd5b610419828261263f565b610458576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044f9061399a565b60405180910390fd5b6000600160038460405161046c91906138ba565b90815260200160405180910390208360405161048891906139f6565b9081526020016040518091039020546104a19190613a3c565b90506000600180856040516104b691906138ba565b9081526020016040518091039020805490506104d29190613a3c565b905080821461069f576001846040516104eb91906138ba565b9081526020016040518091039020818154811061050b5761050a613a70565b5b9060005260206000200160018560405161052591906138ba565b9081526020016040518091039020838154811061054557610544613a70565b5b90600052602060002001908161055b9190613cd6565b5060028460405161056c91906138ba565b9081526020016040518091039020818154811061058c5761058b613a70565b5b90600052602060002090600302016002856040516105aa91906138ba565b908152602001604051809103902083815481106105ca576105c9613a70565b5b9060005260206000209060030201600082018160000190816105ec9190613dd4565b50600182018160010190816106019190613dd4565b50600282018160020190816106169190613dd4565b509050506001826106279190613ebc565b60038560405161063791906138ba565b908152602001604051809103902060018660405161065591906138ba565b9081526020016040518091039020838154811061067557610674613a70565b5b9060005260206000200160405161068c9190613f73565b9081526020016040518091039020819055505b6001846040516106af91906138ba565b90815260200160405180910390208054806106cd576106cc613f8a565b5b6001900381819060005260206000200160006106e99190612753565b90556002846040516106fb91906138ba565b908152602001604051809103902080548061071957610718613f8a565b5b60019003818190600052602060002090600302016000808201600061073e9190612753565b60018201600061074e9190612753565b60028201600061075e9190612753565b5050905560038460405161077291906138ba565b90815260200160405180910390208360405161078e91906139f6565b908152602001604051809103902060009055826040516107ae91906139f6565b6040518091039020846040516107c491906138ba565b60405180910390207faf8967c7b21227d5d987c31cd064433b027cf5bd93330cdbe6b62909093c417260405160405180910390a350505050565b606080825167ffffffffffffffff81111561081c5761081b612935565b5b60405190808252806020026020018201604052801561085557816020015b610842612793565b81526020019060019003908161083a5790505b509150825167ffffffffffffffff81111561087357610872612935565b5b6040519080825280602002602001820160405280156108a15781602001602082028036833780820191505090505b50905060005b8351811015610ba4576108d4858583815181106108c7576108c6613a70565b5b602002602001015161263f565b8282815181106108e7576108e6613a70565b5b60200260200101901515908115158152505081818151811061090c5761090b613a70565b5b602002602001015115610b975760028560405161092991906138ba565b9081526020016040518091039020600160038760405161094991906138ba565b908152602001604051809103902086848151811061096a57610969613a70565b5b602002602001015160405161097f91906139f6565b9081526020016040518091039020546109989190613a3c565b815481106109a9576109a8613a70565b5b90600052602060002090600302016040518060600160405290816000820180546109d290613ace565b80601f01602080910402602001604051908101604052809291908181526020018280546109fe90613ace565b8015610a4b5780601f10610a2057610100808354040283529160200191610a4b565b820191906000526020600020905b815481529060010190602001808311610a2e57829003601f168201915b50505050508152602001600182018054610a6490613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054610a9090613ace565b8015610add5780601f10610ab257610100808354040283529160200191610add565b820191906000526020600020905b815481529060010190602001808311610ac057829003601f168201915b50505050508152602001600282018054610af690613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054610b2290613ace565b8015610b6f5780601f10610b4457610100808354040283529160200191610b6f565b820191906000526020600020905b815481529060010190602001808311610b5257829003601f168201915b505050505081525050838281518110610b8b57610b8a613a70565b5b60200260200101819052505b80806001019150506108a7565b509250929050565b600080600090505b600480549050811015610c1a57600160048281548110610bd757610bd6613a70565b5b90600052602060002001604051610bee9190614051565b90815260200160405180910390208054905082610c0b9190613ebc565b91508080600101915050610bb4565b5090565b6060600080600186604051610c3391906138ba565b9081526020016040518091039020905080805490509150818510610ca857600067ffffffffffffffff811115610c6c57610c6b612935565b5b604051908082528060200260200182016040528015610c9f57816020015b6060815260200190600190039081610c8a5790505b50925050610e07565b60008583610cb69190613a3c565b90506000818611610cc75785610cc9565b815b90508067ffffffffffffffff811115610ce557610ce4612935565b5b604051908082528060200260200182016040528015610d1857816020015b6060815260200190600190039081610d035790505b50945060005b81811015610e0257838189610d339190613ebc565b81548110610d4457610d43613a70565b5b906000526020600020018054610d5990613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054610d8590613ace565b8015610dd25780601f10610da757610100808354040283529160200191610dd2565b820191906000526020600020905b815481529060010190602001808311610db557829003601f168201915b5050505050868281518110610dea57610de9613a70565b5b60200260200101819052508080600101915050610d1e565b505050505b935093915050565b610e176125b8565b8051825114610e5b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e52906140da565b60405180910390fd5b6000835111610e9f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e9690614146565b60405180910390fd5b600583604051610eaf91906138ba565b908152602001604051809103902060009054906101000a900460ff16610f3c57600483908060018154018082558091505060019003906000526020600020016000909190919091509081610f0391906141ac565b506001600584604051610f1691906138ba565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b60005b82518110156112e7576000838281518110610f5d57610f5c613a70565b5b60200260200101515111610fa6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9d906142ca565b60405180910390fd5b610fca84848381518110610fbd57610fbc613a70565b5b602002602001015161263f565b6111695760018085604051610fdf91906138ba565b908152602001604051809103902080549050610ffb9190613ebc565b60038560405161100b91906138ba565b908152602001604051809103902084838151811061102c5761102b613a70565b5b602002602001015160405161104191906139f6565b90815260200160405180910390208190555060018460405161106391906138ba565b908152602001604051809103902083828151811061108457611083613a70565b5b60200260200101519080600181540180825580915050600190039060005260206000200160009091909190915090816110bd91906142ea565b506002846040516110ce91906138ba565b90815260200160405180910390208282815181106110ef576110ee613a70565b5b60200260200101519080600181540180825580915050600190039060005260206000209060030201600090919091909150600082015181600001908161113591906142ea565b50602082015181600101908161114b91906142ea565b50604082015181600201908161116191906142ea565b505050611268565b81818151811061117c5761117b613a70565b5b602002602001015160028560405161119491906138ba565b908152602001604051809103902060016003876040516111b491906138ba565b90815260200160405180910390208685815181106111d5576111d4613a70565b5b60200260200101516040516111ea91906139f6565b9081526020016040518091039020546112039190613a3c565b8154811061121457611213613a70565b5b9060005260206000209060030201600082015181600001908161123791906142ea565b50602082015181600101908161124d91906142ea565b50604082015181600201908161126391906142ea565b509050505b82818151811061127b5761127a613a70565b5b602002602001015160405161129091906139f6565b6040518091039020846040516112a691906138ba565b60405180910390207fd00871f87cd1b1b4e50e5be511f060478c06a013856673c392f2adbfa65df8ad60405160405180910390a38080600101915050610f3f565b50505050565b606080600060048054905090508085106113a357600067ffffffffffffffff81111561131c5761131b612935565b5b60405190808252806020026020018201604052801561134f57816020015b606081526020019060019003908161133a5790505b509250600067ffffffffffffffff81111561136d5761136c612935565b5b60405190808252806020026020018201604052801561139b5781602001602082028036833780820191505090505b5091506115b6565b600085826113b19190613a3c565b905060008186116113c257856113c4565b815b90508067ffffffffffffffff8111156113e0576113df612935565b5b60405190808252806020026020018201604052801561141357816020015b60608152602001906001900390816113fe5790505b5094508067ffffffffffffffff8111156114305761142f612935565b5b60405190808252806020026020018201604052801561145e5781602001602082028036833780820191505090505b50935060005b818110156115b2576004818961147a9190613ebc565b8154811061148b5761148a613a70565b5b9060005260206000200180546114a090613ace565b80601f01602080910402602001604051908101604052809291908181526020018280546114cc90613ace565b80156115195780601f106114ee57610100808354040283529160200191611519565b820191906000526020600020905b8154815290600101906020018083116114fc57829003601f168201915b505050505086828151811061153157611530613a70565b5b602002602001018190525060016004828a61154c9190613ebc565b8154811061155d5761155c613a70565b5b906000526020600020016040516115749190614051565b90815260200160405180910390208054905085828151811061159957611598613a70565b5b6020026020010181815250508080600101915050611464565b5050505b9250925092565b60006005826040516115cf91906138ba565b908152602001604051809103902060009054906101000a900460ff169050919050565b6115fa6125b8565b6116046000612687565b565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60608060008060018760405161164591906138ba565b908152602001604051809103902090508080549050915081861061171157600067ffffffffffffffff81111561167e5761167d612935565b5b6040519080825280602002602001820160405280156116b157816020015b606081526020019060019003908161169c5790505b509350600067ffffffffffffffff8111156116cf576116ce612935565b5b60405190808252806020026020018201604052801561170857816020015b6116f5612793565b8152602001906001900390816116ed5790505b50925050611ae9565b6000868361171f9190613a3c565b905060008187116117305786611732565b815b90508067ffffffffffffffff81111561174e5761174d612935565b5b60405190808252806020026020018201604052801561178157816020015b606081526020019060019003908161176c5790505b5095508067ffffffffffffffff81111561179e5761179d612935565b5b6040519080825280602002602001820160405280156117d757816020015b6117c4612793565b8152602001906001900390816117bc5790505b50945060005b81811015611ae4576000818a6117f39190613ebc565b905084818154811061180857611807613a70565b5b90600052602060002001805461181d90613ace565b80601f016020809104026020016040519081016040528092919081815260200182805461184990613ace565b80156118965780601f1061186b57610100808354040283529160200191611896565b820191906000526020600020905b81548152906001019060200180831161187957829003601f168201915b50505050508883815181106118ae576118ad613a70565b5b602002602001018190525060028b6040516118c991906138ba565b908152602001604051809103902081815481106118e9576118e8613a70565b5b906000526020600020906003020160405180606001604052908160008201805461191290613ace565b80601f016020809104026020016040519081016040528092919081815260200182805461193e90613ace565b801561198b5780601f106119605761010080835404028352916020019161198b565b820191906000526020600020905b81548152906001019060200180831161196e57829003601f168201915b505050505081526020016001820180546119a490613ace565b80601f01602080910402602001604051908101604052809291908181526020018280546119d090613ace565b8015611a1d5780601f106119f257610100808354040283529160200191611a1d565b820191906000526020600020905b815481529060010190602001808311611a0057829003601f168201915b50505050508152602001600282018054611a3690613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054611a6290613ace565b8015611aaf5780601f10611a8457610100808354040283529160200191611aaf565b820191906000526020600020905b815481529060010190602001808311611a9257829003601f168201915b505050505081525050878381518110611acb57611aca613a70565b5b60200260200101819052505080806001019150506117dd565b505050505b93509350939050565b611afa6125b8565b600581604051611b0a91906138ba565b908152602001604051809103902060009054906101000a900460ff16611b65576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5c9061392e565b60405180910390fd5b6000600182604051611b7791906138ba565b9081526020016040518091039020905060005b8180549050811015611bfd57600383604051611ba691906138ba565b9081526020016040518091039020828281548110611bc757611bc6613a70565b5b90600052602060002001604051611bde9190613f73565b9081526020016040518091039020600090558080600101915050611b8a565b50600182604051611c0e91906138ba565b90815260200160405180910390206000611c2891906127b4565b600282604051611c3891906138ba565b90815260200160405180910390206000611c5291906127d5565b81604051611c6091906138ba565b60405180910390207f372dd188f66e03080f62fc207ebf4954a98c81128ca2472aedfc273f0f7c788360405160405180910390a25050565b611ca0612793565b6000611cac848461263f565b90508015611efc57600284604051611cc491906138ba565b90815260200160405180910390206001600386604051611ce491906138ba565b908152602001604051809103902085604051611d0091906139f6565b908152602001604051809103902054611d199190613a3c565b81548110611d2a57611d29613a70565b5b9060005260206000209060030201604051806060016040529081600082018054611d5390613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054611d7f90613ace565b8015611dcc5780601f10611da157610100808354040283529160200191611dcc565b820191906000526020600020905b815481529060010190602001808311611daf57829003601f168201915b50505050508152602001600182018054611de590613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054611e1190613ace565b8015611e5e5780601f10611e3357610100808354040283529160200191611e5e565b820191906000526020600020905b815481529060010190602001808311611e4157829003601f168201915b50505050508152602001600282018054611e7790613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054611ea390613ace565b8015611ef05780601f10611ec557610100808354040283529160200191611ef0565b820191906000526020600020905b815481529060010190602001808311611ed357829003601f168201915b50505050508152505091505b9250929050565b6000600480549050905090565b6000600182604051611f2291906138ba565b9081526020016040518091039020805490509050919050565b6000611f47838361263f565b905092915050565b611f576125b8565b60005b60048054905081101561214557600060048281548110611f7d57611f7c613a70565b5b906000526020600020018054611f9290613ace565b80601f0160208091040260200160405190810160405280929190818152602001828054611fbe90613ace565b801561200b5780601f10611fe05761010080835404028352916020019161200b565b820191906000526020600020905b815481529060010190602001808311611fee57829003601f168201915b50505050509050600060018260405161202491906138ba565b9081526020016040518091039020905060005b81805490508110156120aa5760038360405161205391906138ba565b908152602001604051809103902082828154811061207457612073613a70565b5b9060005260206000200160405161208b9190613f73565b9081526020016040518091039020600090558080600101915050612037565b506001826040516120bb91906138ba565b908152602001604051809103902060006120d591906127b4565b6002826040516120e591906138ba565b908152602001604051809103902060006120ff91906127d5565b600060058360405161211191906138ba565b908152602001604051809103902060006101000a81548160ff02191690831515021790555050508080600101915050611f5a565b506004600061215491906127f9565b7f30addec5e9e954699f286bae8e1558655b0d436c3d7b501014eb5c0b4a76e6db60405160405180910390a1565b61218a6125b8565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036121fc5760006040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016121f39190613690565b60405180910390fd5b61220581612687565b50565b6122106125b8565b6000835111612254576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161224b90614146565b60405180910390fd5b6000825111612298576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161228f906142ca565b60405180910390fd5b6005836040516122a891906138ba565b908152602001604051809103902060009054906101000a900460ff16612335576004839080600181540180825580915050600190039060005260206000200160009091909190915090816122fc91906141ac565b50600160058460405161230f91906138ba565b908152602001604051809103902060006101000a81548160ff0219169083151502179055505b61233f838361263f565b612490576001808460405161235491906138ba565b9081526020016040518091039020805490506123709190613ebc565b60038460405161238091906138ba565b90815260200160405180910390208360405161239c91906139f6565b9081526020016040518091039020819055506001836040516123be91906138ba565b9081526020016040518091039020829080600181540180825580915050600190039060005260206000200160009091909190915090816123fe91906142ea565b5060028360405161240f91906138ba565b9081526020016040518091039020819080600181540180825580915050600190039060005260206000209060030201600090919091909150600082015181600001908161245c91906142ea565b50602082015181600101908161247291906142ea565b50604082015181600201908161248891906142ea565b50505061255b565b806002846040516124a191906138ba565b908152602001604051809103902060016003866040516124c191906138ba565b9081526020016040518091039020856040516124dd91906139f6565b9081526020016040518091039020546124f69190613a3c565b8154811061250757612506613a70565b5b9060005260206000209060030201600082015181600001908161252a91906142ea565b50602082015181600101908161254091906142ea565b50604082015181600201908161255691906142ea565b509050505b8160405161256991906139f6565b60405180910390208360405161257f91906138ba565b60405180910390207fd00871f87cd1b1b4e50e5be511f060478c06a013856673c392f2adbfa65df8ad60405160405180910390a3505050565b6125c061274b565b73ffffffffffffffffffffffffffffffffffffffff166125de611606565b73ffffffffffffffffffffffffffffffffffffffff161461263d5761260161274b565b6040517f118cdaa70000000000000000000000000000000000000000000000000000000081526004016126349190613690565b60405180910390fd5b565b60008060038460405161265291906138ba565b90815260200160405180910390208360405161266e91906139f6565b9081526020016040518091039020541415905092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600033905090565b50805461275f90613ace565b6000825580601f106127715750612790565b601f01602090049060005260206000209081019061278f919061281a565b5b50565b60405180606001604052806060815260200160608152602001606081525090565b50805460008255906000526020600020908101906127d29190612837565b50565b50805460008255600302906000526020600020908101906127f6919061285b565b50565b508054600082559060005260206000209081019061281791906128a2565b50565b5b8082111561283357600081600090555060010161281b565b5090565b5b80821115612857576000818161284e9190612753565b50600101612838565b5090565b5b8082111561289e57600080820160006128759190612753565b6001820160006128859190612753565b6002820160006128959190612753565b5060030161285c565b5090565b5b808211156128c257600081816128b991906128c6565b506001016128a3565b5090565b5080546128d290613ace565b6000825580601f106128e45750612903565b601f016020900490600052602060002090810190612902919061281a565b5b50565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61296d82612924565b810181811067ffffffffffffffff8211171561298c5761298b612935565b5b80604052505050565b600061299f612906565b90506129ab8282612964565b919050565b600067ffffffffffffffff8211156129cb576129ca612935565b5b6129d482612924565b9050602081019050919050565b82818337600083830152505050565b6000612a036129fe846129b0565b612995565b905082815260208101848484011115612a1f57612a1e61291f565b5b612a2a8482856129e1565b509392505050565b600082601f830112612a4757612a4661291a565b5b8135612a578482602086016129f0565b91505092915050565b600067ffffffffffffffff821115612a7b57612a7a612935565b5b612a8482612924565b9050602081019050919050565b6000612aa4612a9f84612a60565b612995565b905082815260208101848484011115612ac057612abf61291f565b5b612acb8482856129e1565b509392505050565b600082601f830112612ae857612ae761291a565b5b8135612af8848260208601612a91565b91505092915050565b60008060408385031215612b1857612b17612910565b5b600083013567ffffffffffffffff811115612b3657612b35612915565b5b612b4285828601612a32565b925050602083013567ffffffffffffffff811115612b6357612b62612915565b5b612b6f85828601612ad3565b9150509250929050565b600067ffffffffffffffff821115612b9457612b93612935565b5b602082029050602081019050919050565b600080fd5b6000612bbd612bb884612b79565b612995565b90508083825260208201905060208402830185811115612be057612bdf612ba5565b5b835b81811015612c2757803567ffffffffffffffff811115612c0557612c0461291a565b5b808601612c128982612ad3565b85526020850194505050602081019050612be2565b5050509392505050565b600082601f830112612c4657612c4561291a565b5b8135612c56848260208601612baa565b91505092915050565b60008060408385031215612c7657612c75612910565b5b600083013567ffffffffffffffff811115612c9457612c93612915565b5b612ca085828601612a32565b925050602083013567ffffffffffffffff811115612cc157612cc0612915565b5b612ccd85828601612c31565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b83811015612d3d578082015181840152602081019050612d22565b60008484015250505050565b6000612d5482612d03565b612d5e8185612d0e565b9350612d6e818560208601612d1f565b612d7781612924565b840191505092915050565b60006060830160008301518482036000860152612d9f8282612d49565b91505060208301518482036020860152612db98282612d49565b91505060408301518482036040860152612dd38282612d49565b9150508091505092915050565b6000612dec8383612d82565b905092915050565b6000602082019050919050565b6000612e0c82612cd7565b612e168185612ce2565b935083602082028501612e2885612cf3565b8060005b85811015612e645784840389528151612e458582612de0565b9450612e5083612df4565b925060208a01995050600181019050612e2c565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b612eb781612ea2565b82525050565b6000612ec98383612eae565b60208301905092915050565b6000602082019050919050565b6000612eed82612e76565b612ef78185612e81565b9350612f0283612e92565b8060005b83811015612f33578151612f1a8882612ebd565b9750612f2583612ed5565b925050600181019050612f06565b5085935050505092915050565b60006040820190508181036000830152612f5a8185612e01565b90508181036020830152612f6e8184612ee2565b90509392505050565b6000819050919050565b612f8a81612f77565b82525050565b6000602082019050612fa56000830184612f81565b92915050565b612fb481612f77565b8114612fbf57600080fd5b50565b600081359050612fd181612fab565b92915050565b600080600060608486031215612ff057612fef612910565b5b600084013567ffffffffffffffff81111561300e5761300d612915565b5b61301a86828701612a32565b935050602061302b86828701612fc2565b925050604061303c86828701612fc2565b9150509250925092565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600061307e8383612d49565b905092915050565b6000602082019050919050565b600061309e82613046565b6130a88185613051565b9350836020820285016130ba85613062565b8060005b858110156130f657848403895281516130d78582613072565b94506130e283613086565b925060208a019950506001810190506130be565b50829750879550505050505092915050565b600060408201905081810360008301526131228185613093565b90506131316020830184612f81565b9392505050565b600067ffffffffffffffff82111561315357613152612935565b5b602082029050602081019050919050565b600080fd5b600080fd5b60006060828403121561318457613183613164565b5b61318e6060612995565b9050600082013567ffffffffffffffff8111156131ae576131ad613169565b5b6131ba84828501612ad3565b600083015250602082013567ffffffffffffffff8111156131de576131dd613169565b5b6131ea84828501612ad3565b602083015250604082013567ffffffffffffffff81111561320e5761320d613169565b5b61321a84828501612ad3565b60408301525092915050565b600061323961323484613138565b612995565b9050808382526020820190506020840283018581111561325c5761325b612ba5565b5b835b818110156132a357803567ffffffffffffffff8111156132815761328061291a565b5b80860161328e898261316e565b8552602085019450505060208101905061325e565b5050509392505050565b600082601f8301126132c2576132c161291a565b5b81356132d2848260208601613226565b91505092915050565b6000806000606084860312156132f4576132f3612910565b5b600084013567ffffffffffffffff81111561331257613311612915565b5b61331e86828701612a32565b935050602084013567ffffffffffffffff81111561333f5761333e612915565b5b61334b86828701612c31565b925050604084013567ffffffffffffffff81111561336c5761336b612915565b5b613378868287016132ad565b9150509250925092565b6000806040838503121561339957613398612910565b5b60006133a785828601612fc2565b92505060206133b885828601612fc2565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b6000613415826133ee565b61341f81856133f9565b935061342f818560208601612d1f565b61343881612924565b840191505092915050565b600061344f838361340a565b905092915050565b6000602082019050919050565b600061346f826133c2565b61347981856133cd565b93508360208202850161348b856133de565b8060005b858110156134c757848403895281516134a88582613443565b94506134b383613457565b925060208a0199505060018101905061348f565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b61350e81612f77565b82525050565b60006135208383613505565b60208301905092915050565b6000602082019050919050565b6000613544826134d9565b61354e81856134e4565b9350613559836134f5565b8060005b8381101561358a5781516135718882613514565b975061357c8361352c565b92505060018101905061355d565b5085935050505092915050565b600060608201905081810360008301526135b18186613464565b905081810360208301526135c58185613539565b90506135d46040830184612f81565b949350505050565b6000602082840312156135f2576135f1612910565b5b600082013567ffffffffffffffff8111156136105761360f612915565b5b61361c84828501612a32565b91505092915050565b61362e81612ea2565b82525050565b60006020820190506136496000830184613625565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061367a8261364f565b9050919050565b61368a8161366f565b82525050565b60006020820190506136a56000830184613681565b92915050565b600060608201905081810360008301526136c58186613093565b905081810360208301526136d98185612e01565b90506136e86040830184612f81565b949350505050565b6000606083016000830151848203600086015261370d8282612d49565b915050602083015184820360208601526137278282612d49565b915050604083015184820360408601526137418282612d49565b9150508091505092915050565b6000604082019050818103600083015261376881856136f0565b90506137776020830184613625565b9392505050565b6137878161366f565b811461379257600080fd5b50565b6000813590506137a48161377e565b92915050565b6000602082840312156137c0576137bf612910565b5b60006137ce84828501613795565b91505092915050565b6000806000606084860312156137f0576137ef612910565b5b600084013567ffffffffffffffff81111561380e5761380d612915565b5b61381a86828701612a32565b935050602084013567ffffffffffffffff81111561383b5761383a612915565b5b61384786828701612ad3565b925050604084013567ffffffffffffffff81111561386857613867612915565b5b6138748682870161316e565b9150509250925092565b600081905092915050565b6000613894826133ee565b61389e818561387e565b93506138ae818560208601612d1f565b80840191505092915050565b60006138c68284613889565b915081905092915050565b600082825260208201905092915050565b7f4e616d65737061636520646f6573206e6f742065786973740000000000000000600082015250565b60006139186018836138d1565b9150613923826138e2565b602082019050919050565b600060208201905081810360008301526139478161390b565b9050919050565b7f4b657920646f6573206e6f7420657869737420696e206e616d65737061636500600082015250565b6000613984601f836138d1565b915061398f8261394e565b602082019050919050565b600060208201905081810360008301526139b381613977565b9050919050565b600081905092915050565b60006139d082612d03565b6139da81856139ba565b93506139ea818560208601612d1f565b80840191505092915050565b6000613a0282846139c5565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000613a4782612f77565b9150613a5283612f77565b9250828203905081811115613a6a57613a69613a0d565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680613ae657607f821691505b602082108103613af957613af8613a9f565b5b50919050565b600081549050613b0e81613ace565b9050919050565b60008190508160005260206000209050919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302613b8c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82613b4f565b613b968683613b4f565b95508019841693508086168417925050509392505050565b6000819050919050565b6000613bd3613bce613bc984612f77565b613bae565b612f77565b9050919050565b6000819050919050565b613bed83613bb8565b613c01613bf982613bda565b848454613b5c565b825550505050565b600090565b613c16613c09565b613c21818484613be4565b505050565b5b81811015613c4557613c3a600082613c0e565b600181019050613c27565b5050565b601f821115613c8a57613c5b81613b15565b613c6484613b3f565b81016020851015613c73578190505b613c87613c7f85613b3f565b830182613c26565b50505b505050565b600082821c905092915050565b6000613cad60001984600802613c8f565b1980831691505092915050565b6000613cc68383613c9c565b9150826002028217905092915050565b818103613ce4575050613dbc565b613ced82613aff565b67ffffffffffffffff811115613d0657613d05612935565b5b613d108254613ace565b613d1b828285613c49565b6000601f831160018114613d4a5760008415613d38578287015490505b613d428582613cba565b865550613db5565b601f198416613d5887613b2a565b9650613d6386613b15565b60005b82811015613d8b57848901548255600182019150600185019450602081019050613d66565b86831015613da85784890154613da4601f891682613c9c565b8355505b6001600288020188555050505b5050505050505b565b600081549050613dcd81613ace565b9050919050565b818103613de2575050613eba565b613deb82613dbe565b67ffffffffffffffff811115613e0457613e03612935565b5b613e0e8254613ace565b613e19828285613c49565b6000601f831160018114613e485760008415613e36578287015490505b613e408582613cba565b865550613eb3565b601f198416613e5687613b15565b9650613e6186613b15565b60005b82811015613e8957848901548255600182019150600185019450602081019050613e64565b86831015613ea65784890154613ea2601f891682613c9c565b8355505b6001600288020188555050505b5050505050505b565b6000613ec782612f77565b9150613ed283612f77565b9250828201905080821115613eea57613ee9613a0d565b5b92915050565b60008154613efd81613ace565b613f0781866139ba565b94506001821660008114613f225760018114613f3757613f6a565b60ff1983168652811515820286019350613f6a565b613f4085613b15565b60005b83811015613f6257815481890152600182019150602081019050613f43565b838801955050505b50505092915050565b6000613f7f8284613ef0565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b60008190508160005260206000209050919050565b60008154613fdb81613ace565b613fe5818661387e565b94506001821660008114614000576001811461401557614048565b60ff1983168652811515820286019350614048565b61401e85613fb9565b60005b8381101561404057815481890152600182019150602081019050614021565b838801955050505b50505092915050565b600061405d8284613fce565b915081905092915050565b7f4b65797320616e642076616c75657320617272617973206d757374206861766560008201527f2073616d65206c656e6774680000000000000000000000000000000000000000602082015250565b60006140c4602c836138d1565b91506140cf82614068565b604082019050919050565b600060208201905081810360008301526140f3816140b7565b9050919050565b7f4e616d6573706163652063616e6e6f7420626520656d70747900000000000000600082015250565b60006141306019836138d1565b915061413b826140fa565b602082019050919050565b6000602082019050818103600083015261415f81614123565b9050919050565b601f8211156141a75761417881613fb9565b61418184613b3f565b81016020851015614190578190505b6141a461419c85613b3f565b830182613c26565b50505b505050565b6141b5826133ee565b67ffffffffffffffff8111156141ce576141cd612935565b5b6141d88254613ace565b6141e3828285614166565b600060209050601f8311600181146142165760008415614204578287015190505b61420e8582613cba565b865550614276565b601f19841661422486613fb9565b60005b8281101561424c57848901518255600182019150602085019450602081019050614227565b868310156142695784890151614265601f891682613c9c565b8355505b6001600288020188555050505b505050505050565b7f4b65792063616e6e6f7420626520656d70747900000000000000000000000000600082015250565b60006142b46013836138d1565b91506142bf8261427e565b602082019050919050565b600060208201905081810360008301526142e3816142a7565b9050919050565b6142f382612d03565b67ffffffffffffffff81111561430c5761430b612935565b5b6143168254613ace565b614321828285613c49565b600060209050601f8311600181146143545760008415614342578287015190505b61434c8582613cba565b8655506143b4565b601f19841661436286613b15565b60005b8281101561438a57848901518255600182019150602085019450602081019050614365565b868310156143a757848901516143a3601f891682613c9c565b8355505b6001600288020188555050505b50505050505056fea264697066735822122096c0c5fe64f7732f41c7ca9f43cb55beaffdda0ec283aa67937fd742c931d6e464736f6c63430008180033" diff --git a/systemcontracts/namespace_storage_wrapper.go b/systemcontracts/namespace_storage_wrapper.go new file mode 100644 index 0000000000..2b957cfd27 --- /dev/null +++ b/systemcontracts/namespace_storage_wrapper.go @@ -0,0 +1,85 @@ +package systemcontracts + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" +) + +// NamespaceStorageContractWrapper wraps the NamespaceStorageContract +type NamespaceStorageContractWrapper struct { + contract NamespaceStorageContract + ns string +} + +func NewNamespaceStorageContractWrapper(contractAddress common.Address, backend ContractBackend, owner common.Address, ns string) (*NamespaceStorageContractWrapper, error) { + if ns == "" { + return nil, errors.New("namespace cannot be empty") + } + + contract, err := NewNamespaceStorageContract(contractAddress, backend, owner) + if err != nil { + return nil, errors.Wrap(err, "failed to create NamespaceStorage contract") + } + + return &NamespaceStorageContractWrapper{ + contract: *contract, + ns: ns, + }, nil +} + +// Address returns the contract address +func (ns *NamespaceStorageContractWrapper) Address() common.Address { + return ns.contract.Address() +} + +// Put stores data with the given namespace and key +func (ns *NamespaceStorageContractWrapper) Put(key []byte, value NamespaceGenericValue) error { + return ns.contract.Put(ns.ns, key, value) +} + +// Get retrieves data by namespace and key +func (ns *NamespaceStorageContractWrapper) Get(key []byte) (*NamespaceGetResult, error) { + return ns.contract.Get(ns.ns, key) +} + +// Remove deletes data by namespace and key +func (ns *NamespaceStorageContractWrapper) Remove(key []byte) error { + return ns.contract.Remove(ns.ns, key) +} + +// Exists checks if a key exists in a namespace +func (ns *NamespaceStorageContractWrapper) Exists(key []byte) (bool, error) { + return ns.contract.Exists(ns.ns, key) +} + +// BatchGet retrieves multiple values by their keys within a namespace +func (ns *NamespaceStorageContractWrapper) BatchGet(keys [][]byte) (*NamespaceBatchGetResult, error) { + return ns.contract.BatchGet(ns.ns, keys) +} + +// BatchPut stores multiple key-value pairs in the same namespace +func (ns *NamespaceStorageContractWrapper) BatchPut(keys [][]byte, values []NamespaceGenericValue) error { + return ns.contract.BatchPut(ns.ns, keys, values) +} + +// List retrieves all stored data in a namespace with pagination +func (ns *NamespaceStorageContractWrapper) List(offset, limit *big.Int) (*NamespaceListResult, error) { + return ns.contract.List(ns.ns, offset, limit) +} + +// ListKeys retrieves all keys in a namespace with pagination +func (ns *NamespaceStorageContractWrapper) ListKeys(offset, limit *big.Int) (*NamespaceListKeysResult, error) { + return ns.contract.ListKeys(ns.ns, offset, limit) +} + +// Count returns the number of items in the namespace +func (ns *NamespaceStorageContractWrapper) Count() (*big.Int, error) { + return ns.contract.CountInNamespace(ns.ns) +} + +// Clear removes all data in the namespace +func (ns *NamespaceStorageContractWrapper) Clear() error { + return ns.contract.ClearNamespace(ns.ns) +} diff --git a/systemcontracts/storage.go b/systemcontracts/storage.go new file mode 100644 index 0000000000..278ab80b53 --- /dev/null +++ b/systemcontracts/storage.go @@ -0,0 +1,114 @@ +package systemcontracts + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/pkg/errors" +) + +type ( + + // GenericValue represents the value structure in the GenericStorage contract + GenericValue struct { + PrimaryData []byte `json:"primaryData"` + SecondaryData []byte `json:"secondaryData"` + AuxiliaryData []byte `json:"auxiliaryData"` + } + + // GenericValueContainer is an interface for objects that can be encoded/decoded to/from GenericValue + GenericValueContainer interface { + Decode(data GenericValue) error + Encode() (GenericValue, error) + } + + // GenericValueObjectIterator is an iterator for GenericValue objects + GenericValueObjectIterator struct { + keys [][]byte + values []GenericValue + exists []bool + cur int + } + + // BatchGetResult represents the result of a batch get operation + BatchGetResult struct { + Values []GenericValue `json:"values"` + ExistsFlags []bool `json:"existsFlags"` + } + + // ListResult represents the result of a list operation + ListResult struct { + KeyList [][]byte `json:"keyList"` + Values []GenericValue `json:"values"` + Total *big.Int `json:"total"` + } + + // ListKeysResult represents the result of a listKeys operation + ListKeysResult struct { + KeyList [][]byte `json:"keyList"` + Total *big.Int `json:"total"` + } + + // GetResult represents the result of a get operation + GetResult struct { + Value GenericValue `json:"value"` + KeyExists bool `json:"keyExists"` + } + // StorageContract provides an interface to interact with the storage smart contract + StorageContract interface { + Address() common.Address + Put(key []byte, value GenericValue) error + Get(key []byte) (*GetResult, error) + Remove(key []byte) error + Exists(key []byte) (bool, error) + List(uint64, uint64) (*ListResult, error) + ListKeys(uint64, uint64) (*ListKeysResult, error) + BatchGet(keys [][]byte) (*BatchGetResult, error) + Count() (*big.Int, error) + } +) + +// DecodeGenericValue decodes a GenericValue into a specific object +func DecodeGenericValue(o interface{}, data GenericValue) error { + if o == nil { + return errors.New("nil object") + } + if oo, ok := o.(GenericValueContainer); ok { + return oo.Decode(data) + } + return errors.New("unsupported object type") +} + +// NewGenericValueObjectIterator creates a new GenericValueObjectIterator +func NewGenericValueObjectIterator(keys [][]byte, values []GenericValue, exists []bool) (state.Iterator, error) { + return &GenericValueObjectIterator{ + keys: keys, + values: values, + exists: exists, + cur: 0, + }, nil +} + +// Size returns the size of the iterator +func (gvoi *GenericValueObjectIterator) Size() int { + return len(gvoi.values) +} + +// Next returns the next key-value pair from the iterator +func (gvoi *GenericValueObjectIterator) Next(o interface{}) ([]byte, error) { + if gvoi.cur >= len(gvoi.values) { + return nil, state.ErrOutOfBoundary + } + 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 + } + if err := DecodeGenericValue(o, value); err != nil { + return nil, err + } + return key, nil +}