diff --git a/.github/workflows/full_test.yml b/.github/workflows/full_test.yml index fbe6132c3..3fb01f4e2 100644 --- a/.github/workflows/full_test.yml +++ b/.github/workflows/full_test.yml @@ -29,7 +29,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: '1.19' - name: Install dependencies run: | diff --git a/.github/workflows/manual_test.yml b/.github/workflows/manual_test.yml index 38d09e716..74192e24d 100644 --- a/.github/workflows/manual_test.yml +++ b/.github/workflows/manual_test.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: '1.19' - name: Install dependencies run: | diff --git a/.github/workflows/short_test.yml b/.github/workflows/short_test.yml index b957fef90..6ed2b6a02 100644 --- a/.github/workflows/short_test.yml +++ b/.github/workflows/short_test.yml @@ -35,7 +35,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: '1.19' - name: Install dependencies run: | diff --git a/account/accountservice.go b/account/accountservice.go index 030179c5c..dc5152a98 100644 --- a/account/accountservice.go +++ b/account/accountservice.go @@ -10,6 +10,7 @@ import ( "github.com/aergoio/aergo/v2/contract/name" "github.com/aergoio/aergo/v2/pkg/component" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/message" ) @@ -63,7 +64,7 @@ func (as *AccountService) Statistics() *map[string]interface{} { } } func (as *AccountService) resolveName(namedAddress []byte) ([]byte, error) { - scs, err := as.sdb.GetStateDB().GetNameAccountState() + scs, err := statedb.GetNameAccountState(as.sdb.GetStateDB()) if err != nil { return nil, err } diff --git a/chain/chaindb.go b/chain/chaindb.go index 52b50495c..b48c11bb1 100644 --- a/chain/chaindb.go +++ b/chain/chaindb.go @@ -647,7 +647,7 @@ func (cdb *ChainDB) getReceipts(blockHash []byte, blockNo types.BlockNo, hardForkConfig *config.HardforkConfig) (*types.Receipts, error) { data := cdb.store.Get(dbkey.Receipts(blockHash, blockNo)) if len(data) == 0 { - return nil, errors.New("cannot find a receipt") + return nil, fmt.Errorf("empty : blockNo=%d", blockNo) } var receipts types.Receipts diff --git a/chain/chainhandle.go b/chain/chainhandle.go index df526505f..0a35e7082 100644 --- a/chain/chainhandle.go +++ b/chain/chainhandle.go @@ -22,6 +22,7 @@ import ( "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/internal/enc/proto" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/message" ) @@ -136,6 +137,72 @@ func (cs *ChainService) getReceipt(txHash []byte) (*types.Receipt, error) { return r, nil } +func (cs *ChainService) getReceipts(blockHash []byte) (*types.Receipts, error) { + block, err := cs.cdb.getBlock(blockHash) + if err != nil { + return nil, &ErrNoBlock{blockHash} + } + + blockInMainChain, err := cs.cdb.GetBlockByNo(block.Header.BlockNo) + if !bytes.Equal(block.BlockHash(), blockInMainChain.BlockHash()) { + return nil, errors.New("cannot find a receipt") + } + + receipts, err := cs.cdb.getReceipts(block.BlockHash(), block.GetHeader().BlockNo, cs.cfg.Hardfork) + if err != nil { + return nil, err + } + + for idx, r := range receipts.Get() { + r.SetMemoryInfo(blockHash, block.Header.BlockNo, int32(idx)) + + r.ContractAddress = types.AddressOrigin(r.ContractAddress) + + for _, tx := range block.GetBody().GetTxs() { + if bytes.Equal(r.GetTxHash(), tx.GetHash()) { + r.From = tx.GetBody().GetAccount() + r.To = tx.GetBody().GetRecipient() + break + } + } + } + + return receipts, nil +} + +func (cs *ChainService) getReceiptsByNo(blockNo types.BlockNo) (*types.Receipts, error) { + blockInMainChain, err := cs.cdb.GetBlockByNo(blockNo) + if err != nil { + return nil, &ErrNoBlock{blockNo} + } + + block, err := cs.cdb.getBlock(blockInMainChain.BlockHash()) + if !bytes.Equal(block.BlockHash(), blockInMainChain.BlockHash()) { + return nil, errors.New("cannot find a receipt") + } + + receipts, err := cs.cdb.getReceipts(block.BlockHash(), block.GetHeader().BlockNo, cs.cfg.Hardfork) + if err != nil { + return nil, err + } + + for idx, r := range receipts.Get() { + r.SetMemoryInfo(blockInMainChain.BlockHash(), blockNo, int32(idx)) + + r.ContractAddress = types.AddressOrigin(r.ContractAddress) + + for _, tx := range block.GetBody().GetTxs() { + if bytes.Equal(r.GetTxHash(), tx.GetHash()) { + r.From = tx.GetBody().GetAccount() + r.To = tx.GetBody().GetRecipient() + break + } + } + } + + return receipts, nil +} + func (cs *ChainService) getEvents(events *[]*types.Event, blkNo types.BlockNo, filter *types.FilterInfo, argFilter []types.ArgFilter) uint64 { blkHash, err := cs.cdb.getHashByNo(blkNo) @@ -624,7 +691,7 @@ func newBlockExecutor(cs *ChainService, bState *state.BlockState, block *types.B } // NewTxExecutor returns a new TxExecFn. -func NewTxExecutor(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb contract.ChainAccessor, bi *types.BlockHeaderInfo, preloadService int) TxExecFn { +func NewTxExecutor(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb contract.ChainAccessor, bi *types.BlockHeaderInfo, executionMode int) TxExecFn { return func(bState *state.BlockState, tx types.Transaction) error { if bState == nil { logger.Error().Msg("bstate is nil in txExec") @@ -636,7 +703,7 @@ func NewTxExecutor(execCtx context.Context, ccc consensus.ChainConsensusCluster, } blockSnap := bState.Snapshot() - err := executeTx(execCtx, ccc, cdb, bState, tx, bi, preloadService) + err := executeTx(execCtx, ccc, cdb, bState, tx, bi, executionMode) if err != nil { logger.Error().Err(err).Str("hash", base58.Encode(tx.GetHash())).Msg("tx failed") if err2 := bState.Rollback(blockSnap); err2 != nil { @@ -653,22 +720,14 @@ func (e *blockExecutor) execute() error { // Receipt must be committed unconditionally. if !e.commitOnly { defer contract.CloseDatabase() - var preloadTx *types.Tx - numTxs := len(e.txs) - for i, tx := range e.txs { - // if tx is not the last one, preload the next tx - if i != numTxs-1 { - preloadTx = e.txs[i+1] - contract.RequestPreload(e.BlockState, e.bi, preloadTx, tx, contract.ChainService) - } + logger.Trace().Int("txCount", len(e.txs)).Msg("executing txs") + for _, tx := range e.txs { // execute the transaction if err := e.execTx(e.BlockState, types.NewTransaction(tx)); err != nil { //FIXME maybe system error. restart or panic // all txs have executed successfully in BP node return err } - // mark the next preload tx to be executed - contract.SetPreloadTx(preloadTx, contract.ChainService) } if e.validateSignWait != nil { @@ -870,7 +929,7 @@ func adjustRv(ret string) string { return ret } -func resetAccount(account *state.V, fee *big.Int, nonce *uint64) error { +func resetAccount(account *state.AccountState, fee *big.Int, nonce *uint64) error { account.Reset() if fee != nil { if account.Balance().Cmp(fee) < 0 { @@ -884,7 +943,7 @@ func resetAccount(account *state.V, fee *big.Int, nonce *uint64) error { return account.PutState() } -func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb contract.ChainAccessor, bs *state.BlockState, tx types.Transaction, bi *types.BlockHeaderInfo, preloadService int) error { +func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb contract.ChainAccessor, bs *state.BlockState, tx types.Transaction, bi *types.BlockHeaderInfo, executionMode int) error { var ( txBody = tx.GetBody() isQuirkTx = types.IsQuirkTx(tx.GetHash()) @@ -910,7 +969,7 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb return err } - sender, err := bs.GetAccountStateV(account) + sender, err := state.GetAccountState(account, bs.StateDB) if err != nil { return err } @@ -923,16 +982,16 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb if recipient, err = name.Resolve(bs, txBody.Recipient, isQuirkTx); err != nil { return err } - var receiver *state.V + var receiver *state.AccountState status := "SUCCESS" if len(recipient) > 0 { - receiver, err = bs.GetAccountStateV(recipient) + receiver, err = state.GetAccountState(recipient, bs.StateDB) if receiver != nil && txBody.Type == types.TxType_REDEPLOY { status = "RECREATED" receiver.SetRedeploy() } } else { - receiver, err = bs.CreateAccountStateV(contract.CreateContractID(txBody.Account, txBody.Nonce)) + receiver, err = state.CreateAccountState(contract.CreateContractID(txBody.Account, txBody.Nonce), bs.StateDB) status = "CREATED" } if err != nil { @@ -944,7 +1003,7 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb var events []*types.Event switch txBody.Type { case types.TxType_NORMAL, types.TxType_REDEPLOY, types.TxType_TRANSFER, types.TxType_CALL, types.TxType_DEPLOY: - rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, preloadService, false) + rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, executionMode, false) sender.SubBalance(txFee) case types.TxType_GOVERNANCE: txFee = new(big.Int).SetUint64(0) @@ -958,8 +1017,8 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb return err } - var contractState *state.ContractState - contractState, err = bs.OpenContractState(receiver.AccountID(), receiver.State()) + var contractState *statedb.ContractState + contractState, err = statedb.OpenContractState(receiver.ID(), receiver.State(), bs.StateDB) if err != nil { return err } @@ -972,7 +1031,7 @@ func executeTx(execCtx context.Context, ccc consensus.ChainConsensusCluster, cdb } return types.ErrNotAllowedFeeDelegation } - rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, preloadService, true) + rv, events, txFee, err = contract.Execute(execCtx, bs, cdb, tx.GetTx(), sender, receiver, bi, executionMode, true) receiver.SubBalance(txFee) } @@ -1055,22 +1114,19 @@ func sendRewardCoinbase(bState *state.BlockState, coinbaseAccount []byte) error return nil } - receiverID := types.ToAccountID(coinbaseAccount) - receiverState, err := bState.GetAccountState(receiverID) + // add bp reward to coinbase account + coinbaseAccountState, err := state.GetAccountState(coinbaseAccount, bState.StateDB) if err != nil { return err } - - receiverChange := types.State(*receiverState) - receiverChange.Balance = new(big.Int).Add(receiverChange.GetBalanceBigInt(), bpReward).Bytes() - - err = bState.PutState(receiverID, &receiverChange) + coinbaseAccountState.AddBalance(bpReward) + err = coinbaseAccountState.PutState() if err != nil { return err } logger.Debug().Str("reward", bpReward.String()). - Str("newbalance", receiverChange.GetBalanceBigInt().String()).Msg("send reward to coinbase account") + Str("newbalance", coinbaseAccountState.Balance().String()).Msg("send reward to coinbase account") return nil } diff --git a/chain/chainservice.go b/chain/chainservice.go index a9631001b..d69fd8af7 100644 --- a/chain/chainservice.go +++ b/chain/chainservice.go @@ -27,6 +27,7 @@ import ( "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/pkg/component" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/dbkey" "github.com/aergoio/aergo/v2/types/message" @@ -179,6 +180,8 @@ type IChainHandler interface { getBlockByNo(blockNo types.BlockNo) (*types.Block, error) getTx(txHash []byte) (*types.Tx, *types.TxIdx, error) getReceipt(txHash []byte) (*types.Receipt, error) + getReceipts(blockHash []byte) (*types.Receipts, error) + getReceiptsByNo(blockNo types.BlockNo) (*types.Receipts, error) getAccountVote(addr []byte) (*types.AccountVoteInfo, error) getVotes(id string, n uint32) (*types.VoteList, error) getStaking(addr []byte) (*types.Staking, error) @@ -231,6 +234,11 @@ func NewChainService(cfg *cfg.Config) *ChainService { logger.Panic().Err(err).Msg("failed to initialize DB") } + // check legacy format about trie key + if legacy := cs.SDB().GetStateDB().IsLegacyTrieKey(); legacy { + logger.Panic().Msg("Legacy key format detected. Clear existing data and restart.") + } + if err = Init(cfg.Blockchain.MaxBlockSize, cfg.Blockchain.CoinbaseAccount, cfg.Consensus.EnableBp, @@ -294,7 +302,8 @@ func NewChainService(cfg *cfg.Config) *ChainService { types.InitGovernance(cs.ConsensusType(), cs.IsPublic()) //reset parameter of aergo.system - systemState, err := cs.SDB().GetSystemAccountState() + + systemState, err := statedb.GetSystemAccountState(cs.SDB().GetStateDB()) if err != nil { logger.Panic().Err(err).Msg("failed to read aergo.system state") } @@ -433,6 +442,8 @@ func (cs *ChainService) Receive(context actor.Context) { *message.GetStateAndProof, *message.GetTx, *message.GetReceipt, + *message.GetReceipts, + *message.GetReceiptsByNo, *message.GetABI, *message.GetQuery, *message.GetStateQuery, @@ -495,10 +506,14 @@ func (cs *ChainService) getVotes(id string, n uint32) (*types.VoteList, error) { switch ConsensusName() { case consensus.ConsensusName[consensus.ConsensusDPOS]: sdb := cs.sdb.OpenNewStateDB(cs.sdb.GetRoot()) + scs, err := statedb.GetSystemAccountState(sdb) + if err != nil { + return nil, err + } if n == 0 { - return system.GetVoteResult(sdb, []byte(id), system.GetBpCount()) + return system.GetVoteResult(scs, []byte(id), system.GetBpCount()) } - return system.GetVoteResult(sdb, []byte(id), int(n)) + return system.GetVoteResult(scs, []byte(id), int(n)) case consensus.ConsensusName[consensus.ConsensusRAFT]: //return cs.GetBPs() return nil, ErrNotSupportedConsensus @@ -513,11 +528,11 @@ func (cs *ChainService) getAccountVote(addr []byte) (*types.AccountVoteInfo, err } sdb := cs.sdb.OpenNewStateDB(cs.sdb.GetRoot()) - scs, err := sdb.GetSystemAccountState() + scs, err := statedb.GetSystemAccountState(sdb) if err != nil { return nil, err } - namescs, err := sdb.GetNameAccountState() + namescs, err := statedb.GetNameAccountState(sdb) if err != nil { return nil, err } @@ -535,11 +550,11 @@ func (cs *ChainService) getStaking(addr []byte) (*types.Staking, error) { } sdb := cs.sdb.OpenNewStateDB(cs.sdb.GetRoot()) - scs, err := sdb.GetSystemAccountState() + scs, err := statedb.GetSystemAccountState(sdb) if err != nil { return nil, err } - namescs, err := sdb.GetNameAccountState() + namescs, err := statedb.GetNameAccountState(sdb) if err != nil { return nil, err } @@ -551,7 +566,7 @@ func (cs *ChainService) getStaking(addr []byte) (*types.Staking, error) { } func (cs *ChainService) getNameInfo(qname string, blockNo types.BlockNo) (*types.NameInfo, error) { - var stateDB *state.StateDB + var stateDB *statedb.StateDB if blockNo != 0 { block, err := cs.cdb.GetBlockByNo(blockNo) if err != nil { @@ -561,22 +576,34 @@ func (cs *ChainService) getNameInfo(qname string, blockNo types.BlockNo) (*types } else { stateDB = cs.sdb.OpenNewStateDB(cs.sdb.GetRoot()) } - return name.GetNameInfo(stateDB, qname) + + ncs, err := statedb.GetNameAccountState(stateDB) + if err != nil { + return nil, err + } + return name.GetNameInfo(ncs, qname) } func (cs *ChainService) getEnterpriseConf(key string) (*types.EnterpriseConfig, error) { sdb := cs.sdb.OpenNewStateDB(cs.sdb.GetRoot()) + ecs, err := statedb.GetEnterpriseAccountState(sdb) + if err != nil { + return nil, err + } if strings.ToUpper(key) != string(dbkey.EnterpriseAdmins()) { - return enterprise.GetConf(sdb, key) + return enterprise.GetConf(ecs, key) } - return enterprise.GetAdmin(sdb) + return enterprise.GetAdmin(ecs) } func (cs *ChainService) getSystemValue(key types.SystemValue) (*big.Int, error) { - stateDB := cs.sdb.GetStateDB() switch key { case types.StakingTotal: - return system.GetStakingTotal(stateDB) + scs, err := statedb.GetSystemAccountState(cs.sdb.GetStateDB()) + if err != nil { + return nil, err + } + return system.GetStakingTotal(scs) case types.StakingMin: return system.GetStakingMinimum(), nil case types.GasPrice: @@ -682,9 +709,9 @@ func (cm *ChainManager) Receive(context actor.Context) { } } -func getAddressNameResolved(sdb *state.StateDB, account []byte) ([]byte, error) { +func getAddressNameResolved(sdb *statedb.StateDB, account []byte) ([]byte, error) { if len(account) == types.NameLength { - scs, err := sdb.GetNameAccountState() + scs, err := statedb.GetNameAccountState(sdb) if err != nil { logger.Error().Str("hash", base58.Encode(account)).Err(err).Msg("failed to get state for account") return nil, err @@ -695,9 +722,9 @@ func getAddressNameResolved(sdb *state.StateDB, account []byte) ([]byte, error) } func (cw *ChainWorker) Receive(context actor.Context) { - var sdb *state.StateDB + var sdb *statedb.StateDB - getAccProof := func(sdb *state.StateDB, account, root []byte, compressed bool) (*types.AccountProof, error) { + getAccProof := func(sdb *statedb.StateDB, account, root []byte, compressed bool) (*types.AccountProof, error) { address, err := getAddressNameResolved(sdb, account) if err != nil { return nil, err @@ -773,6 +800,18 @@ func (cw *ChainWorker) Receive(context actor.Context) { Receipt: receipt, Err: err, }) + case *message.GetReceipts: + receipts, err := cw.getReceipts(msg.BlockHash) + context.Respond(message.GetReceiptsRsp{ + Receipts: receipts, + Err: err, + }) + case *message.GetReceiptsByNo: + receipts, err := cw.getReceiptsByNo(msg.BlockNo) + context.Respond(message.GetReceiptsByNoRsp{ + Receipts: receipts, + Err: err, + }) case *message.GetABI: sdb = cw.sdb.OpenNewStateDB(cw.sdb.GetRoot()) address, err := getAddressNameResolved(sdb, msg.Contract) @@ -783,7 +822,7 @@ func (cw *ChainWorker) Receive(context actor.Context) { }) break } - contractState, err := sdb.OpenContractStateAccount(types.ToAccountID(address)) + contractState, err := statedb.OpenContractStateAccount(address, sdb) if err == nil { abi, err := contract.GetABI(contractState, nil) context.Respond(message.GetABIRsp{ @@ -805,7 +844,7 @@ func (cw *ChainWorker) Receive(context actor.Context) { context.Respond(message.GetQueryRsp{Result: nil, Err: err}) break } - ctrState, err := sdb.OpenContractStateAccount(types.ToAccountID(address)) + ctrState, err := statedb.OpenContractStateAccount(address, sdb) if err != nil { logger.Error().Str("hash", base58.Encode(address)).Err(err).Msg("failed to get state for contract") context.Respond(message.GetQueryRsp{Result: nil, Err: err}) @@ -893,7 +932,7 @@ func (cw *ChainWorker) Receive(context actor.Context) { defer runtime.UnlockOSThread() sdb = cw.sdb.OpenNewStateDB(cw.sdb.GetRoot()) - ctrState, err := sdb.OpenContractStateAccount(types.ToAccountID(msg.Contract)) + ctrState, err := statedb.OpenContractStateAccount(msg.Contract, sdb) if err != nil { logger.Error().Str("hash", base58.Encode(msg.Contract)).Err(err).Msg("failed to get state for contract") context.Respond(message.CheckFeeDelegationRsp{Err: err}) diff --git a/chain/governance.go b/chain/governance.go index fcc6457a6..994bb1bda 100644 --- a/chain/governance.go +++ b/chain/governance.go @@ -14,10 +14,11 @@ import ( "github.com/aergoio/aergo/v2/contract/name" "github.com/aergoio/aergo/v2/contract/system" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" ) -func executeGovernanceTx(ccc consensus.ChainConsensusCluster, bs *state.BlockState, txBody *types.TxBody, sender, receiver *state.V, +func executeGovernanceTx(ccc consensus.ChainConsensusCluster, bs *state.BlockState, txBody *types.TxBody, sender, receiver *state.AccountState, blockInfo *types.BlockHeaderInfo) ([]*types.Event, error) { if len(txBody.Payload) <= 0 { @@ -25,8 +26,7 @@ func executeGovernanceTx(ccc consensus.ChainConsensusCluster, bs *state.BlockSta } governance := string(txBody.Recipient) - - scs, err := bs.StateDB.OpenContractState(receiver.AccountID(), receiver.State()) + scs, err := statedb.OpenContractState(receiver.IDNoPadding(), receiver.State(), bs.StateDB) if err != nil { return nil, err } @@ -47,7 +47,7 @@ func executeGovernanceTx(ccc consensus.ChainConsensusCluster, bs *state.BlockSta err = types.ErrTxInvalidRecipient } if err == nil { - err = bs.StateDB.StageContractState(scs) + err = statedb.StageContractState(scs, bs.StateDB) } return events, err @@ -55,8 +55,8 @@ func executeGovernanceTx(ccc consensus.ChainConsensusCluster, bs *state.BlockSta // InitGenesisBPs opens system contract and put initial voting result // it also set *State in Genesis to use statedb -func InitGenesisBPs(states *state.StateDB, genesis *types.Genesis) error { - scs, err := states.GetSystemAccountState() +func InitGenesisBPs(states *statedb.StateDB, genesis *types.Genesis) error { + scs, err := statedb.GetSystemAccountState(states) if err != nil { return err } @@ -72,7 +72,7 @@ func InitGenesisBPs(states *state.StateDB, genesis *types.Genesis) error { // Set genesis.BPs to the votes-ordered BPs. This will be used later for // bootstrapping. genesis.BPs = system.BuildOrderedCandidates(voteResult) - if err = states.StageContractState(scs); err != nil { + if err = statedb.StageContractState(scs, states); err != nil { return err } if err = states.Update(); err != nil { diff --git a/chain/reorg.go b/chain/reorg.go index 7f3f3f867..41babf9b4 100644 --- a/chain/reorg.go +++ b/chain/reorg.go @@ -10,6 +10,7 @@ import ( "github.com/aergoio/aergo/v2/contract/system" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/message" ) @@ -168,7 +169,7 @@ func (cs *ChainService) reorg(topBlock *types.Block, marker *ReorgMarker) error } cs.stat.updateEvent(ReorgStat, time.Since(begT), reorg.oldBlocks[0], reorg.newBlocks[0], reorg.brStartBlock) - systemStateDB, err := cs.SDB().GetSystemAccountState() + systemStateDB, err := statedb.GetSystemAccountState(cs.SDB().GetStateDB()) system.InitSystemParams(systemStateDB, system.RESET) logger.Info().Msg("reorg end") diff --git a/chain/signVerifier.go b/chain/signVerifier.go index a9ce80512..330ca7989 100644 --- a/chain/signVerifier.go +++ b/chain/signVerifier.go @@ -10,6 +10,7 @@ import ( "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/pkg/component" "github.com/aergoio/aergo/v2/state" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/aergoio/aergo/v2/types/message" ) @@ -132,7 +133,7 @@ func (sv *SignVerifier) verifyTx(comm component.IComponentRequester, tx *types.T } if tx.NeedNameVerify() { - cs, err := sv.sdb.GetStateDB().GetNameAccountState() + cs, err := statedb.GetNameAccountState(sv.sdb.GetStateDB()) if err != nil { logger.Error().Err(err).Msg("failed to get verify because of opening contract error") return false, err diff --git a/cmd/aergocli/cmd/accounts.go b/cmd/aergocli/cmd/accounts.go index 69fe50e0c..6c9f10bd9 100644 --- a/cmd/aergocli/cmd/accounts.go +++ b/cmd/aergocli/cmd/accounts.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "os" "path/filepath" @@ -237,7 +236,7 @@ func importKeystore(cmd *cobra.Command) ([]byte, error) { if err != nil { return nil, err } - keystore, err := ioutil.ReadFile(absPath) + keystore, err := os.ReadFile(absPath) if err != nil { return nil, err } diff --git a/cmd/aergocli/cmd/blockchain.go b/cmd/aergocli/cmd/blockchain.go index 0e5b4a579..dd2bb0855 100644 --- a/cmd/aergocli/cmd/blockchain.go +++ b/cmd/aergocli/cmd/blockchain.go @@ -8,8 +8,8 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -30,9 +30,11 @@ var blockchainCmd = &cobra.Command{ return } if printHex { - cmd.Println(util.ConvHexBlockchainStatus(msg)) + res := jsonrpc.ConvHexBlockchainStatus(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } else { - cmd.Println(util.ConvBlockchainStatus(msg)) + res := jsonrpc.ConvBlockchainStatus(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } }, } diff --git a/cmd/aergocli/cmd/blockchain_test.go b/cmd/aergocli/cmd/blockchain_test.go index 271f32234..f5650a9c6 100644 --- a/cmd/aergocli/cmd/blockchain_test.go +++ b/cmd/aergocli/cmd/blockchain_test.go @@ -1,9 +1,9 @@ package cmd import ( + "encoding/json" "testing" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/internal/enc/hex" "github.com/aergoio/aergo/v2/types" @@ -35,8 +35,8 @@ func TestBlockchainWithMock(t *testing.T) { t.Fatal(err) } - assert.Equal(t, testBlockHashString, result["Hash"]) - assert.Equal(t, float64(1), result["Height"]) + assert.Equal(t, testBlockHashString, result["hash"]) + assert.Equal(t, float64(1), result["height"]) output, err = executeCommand(rootCmd, "blockchain", "trashargs") assert.NoError(t, err, "should be success") @@ -49,6 +49,6 @@ func TestBlockchainWithMock(t *testing.T) { t.Fatal(err) } testBlockHashByte, _ := base58.Decode(testBlockHashString) - assert.Equal(t, hex.Encode(testBlockHashByte), result["Hash"]) - assert.Equal(t, float64(1), result["Height"]) + assert.Equal(t, hex.Encode(testBlockHashByte), result["hash"]) + assert.Equal(t, float64(1), result["height"]) } diff --git a/cmd/aergocli/cmd/chaininfo.go b/cmd/aergocli/cmd/chaininfo.go index fce5e3b90..dd95d5f73 100644 --- a/cmd/aergocli/cmd/chaininfo.go +++ b/cmd/aergocli/cmd/chaininfo.go @@ -3,8 +3,8 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -21,6 +21,7 @@ var chaininfoCmd = &cobra.Command{ cmd.Printf("Failed: %s\n", err.Error()) return } - cmd.Println(util.ConvChainInfoMsg(msg)) + res := jsonrpc.ConvChainInfo(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) }, } diff --git a/cmd/aergocli/util/grpccommon.go b/cmd/aergocli/cmd/client.go similarity index 57% rename from cmd/aergocli/util/grpccommon.go rename to cmd/aergocli/cmd/client.go index 11a6b3b93..52d90ed76 100644 --- a/cmd/aergocli/util/grpccommon.go +++ b/cmd/aergocli/cmd/client.go @@ -3,13 +3,11 @@ * @copyright defined in aergo/LICENSE.txt */ -package util +package cmd import ( "fmt" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" - "github.com/aergoio/aergo/v2/internal/enc/proto" "github.com/aergoio/aergo/v2/types" "google.golang.org/grpc" ) @@ -42,22 +40,3 @@ func (c *ConnClient) Close() { c.conn.Close() c.conn = nil } - -// JSON converts protobuf message(struct) to json notation -func JSON(pb proto.Message) string { - jsonout, err := json.MarshalIndent(pb, "", " ") - if err != nil { - fmt.Printf("Failed: %s\n", err.Error()) - return "" - } - return string(jsonout) -} - -func B58JSON(i interface{}) string { - jsonout, err := json.MarshalIndent(i, "", " ") - if err != nil { - fmt.Printf("Failed: %s\n", err.Error()) - return "" - } - return string(jsonout) -} diff --git a/cmd/aergocli/cmd/committx.go b/cmd/aergocli/cmd/committx.go index 9dbdfd8a2..94b382ea4 100644 --- a/cmd/aergocli/cmd/committx.go +++ b/cmd/aergocli/cmd/committx.go @@ -8,10 +8,10 @@ package cmd import ( "context" "errors" - "io/ioutil" + "os" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -50,7 +50,7 @@ func init() { func execCommitTX(cmd *cobra.Command, args []string) error { if jsonPath != "" { - b, readerr := ioutil.ReadFile(jsonPath) + b, readerr := os.ReadFile(jsonPath) if readerr != nil { return errors.New("Failed to read --jsontxpath\n" + readerr.Error()) } @@ -59,7 +59,7 @@ func execCommitTX(cmd *cobra.Command, args []string) error { if jsonTx != "" { var msg *types.CommitResultList - txlist, err := util.ParseBase58Tx([]byte(jsonTx)) + txlist, err := jsonrpc.ParseBase58Tx([]byte(jsonTx)) if err != nil { return errors.New("Failed to parse --jsontx\n" + err.Error()) } @@ -67,7 +67,8 @@ func execCommitTX(cmd *cobra.Command, args []string) error { if err != nil { return errors.New("Failed request to aergo server\n" + err.Error()) } - cmd.Println(util.JSON(msg)) + res := jsonrpc.ConvCommitResultList(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } return nil } diff --git a/cmd/aergocli/cmd/committx_test.go b/cmd/aergocli/cmd/committx_test.go index aa5eb00fd..3b3cc2999 100644 --- a/cmd/aergocli/cmd/committx_test.go +++ b/cmd/aergocli/cmd/committx_test.go @@ -1,11 +1,12 @@ package cmd import ( + "encoding/json" "testing" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -34,10 +35,10 @@ func TestCommitTxWithMock(t *testing.T) { ).MaxTimes(1) output, err = executeCommand(rootCmd, "committx", "--jsontx", "{\"Body\":{}}") - out := &types.CommitResultList{} + out := &jsonrpc.InOutCommitResultList{} err = json.Unmarshal([]byte(output), out) assert.NoError(t, err, "commit output is invalid") - assert.Equal(t, "tx invalid format", out.GetResults()[0].Detail) + assert.Equal(t, "tx invalid format", out.Results[0].Detail) mock.EXPECT().CommitTX( gomock.Any(), // expect any value for first parameter @@ -56,5 +57,5 @@ func TestCommitTxWithMock(t *testing.T) { output, err = executeCommand(rootCmd, "committx", "--jsontx", "{ \"Hash\": \"HB44gJvHhVoEfgiGq3VZmV9VUXfBXhHjcEvroBMkJGnY\", \"Body\": {\"Nonce\": 2, \"Account\": \"AmNBZ8WQKP8DbuP9Q9W9vGFhiT8vQNcuSZ2SbBbVvbJWGV3Wh1mn\", \"Recipient\": \"AmLnVfGwq49etaa7dnzfGJTbaZWV7aVmrxFes4KmWukXwtooVZPJ\", \"Amount\": \"25000\", \"Payload\": \"\", \"Limit\": 100, \"Price\": \"1\", \"Type\": 0, \"Sign\": \"381yXYxTtq2tRPRQPF7tHH6Cq3y8PvcsFWztPwCRmmYfqnK83Z3a6Yj9fyy8Rpvrrw76Y52SNAP6Th3BYQjX1Bcmf6NQrDHQ\"}}") err = json.Unmarshal([]byte(output), out) assert.NoError(t, err, "should no error") - assert.Equal(t, "HB44gJvHhVoEfgiGq3VZmV9VUXfBXhHjcEvroBMkJGnY", base58.Encode(out.GetResults()[0].Hash)) + assert.Equal(t, "HB44gJvHhVoEfgiGq3VZmV9VUXfBXhHjcEvroBMkJGnY", out.Results[0].Hash) } diff --git a/cmd/aergocli/cmd/contract.go b/cmd/aergocli/cmd/contract.go index 010b7f6ac..9d0b6f4cb 100644 --- a/cmd/aergocli/cmd/contract.go +++ b/cmd/aergocli/cmd/contract.go @@ -6,10 +6,9 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "strconv" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" luacEncoding "github.com/aergoio/aergo/v2/cmd/aergoluac/encoding" luac "github.com/aergoio/aergo/v2/cmd/aergoluac/util" "github.com/aergoio/aergo/v2/internal/common" @@ -17,11 +16,12 @@ import ( "github.com/aergoio/aergo/v2/internal/enc/hex" "github.com/aergoio/aergo/v2/types" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) var ( - client *util.ConnClient + client *ConnClient admClient types.AdminRPCServiceClient data string nonce uint64 @@ -170,12 +170,12 @@ func runDeployCmd(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = false return errors.New("not enough arguments") } - code, err = ioutil.ReadFile(args[1]) + code, err = os.ReadFile(args[1]) if err != nil { return fmt.Errorf("failed to read code file: %v", err.Error()) } var abi []byte - abi, err = ioutil.ReadFile(args[2]) + abi, err = os.ReadFile(args[2]) if err != nil { return fmt.Errorf("failed to read abi file: %v", err.Error()) } @@ -212,7 +212,7 @@ func runDeployCmd(cmd *cobra.Command, args []string) error { } } - amountBigInt, err := util.ParseUnit(amount) + amountBigInt, err := jsonrpc.ParseUnit(amount) if err != nil { return fmt.Errorf("failed to parse amount: %v", err.Error()) } @@ -284,7 +284,7 @@ func runCallCmd(cmd *cobra.Command, args []string) error { } } - amountBigInt, err := util.ParseUnit(amount) + amountBigInt, err := jsonrpc.ParseUnit(amount) if err != nil { return fmt.Errorf("failed to parse amount: %v", err) } @@ -342,7 +342,8 @@ func runCallCmd(cmd *cobra.Command, args []string) error { } if toJSON { - cmd.Println(util.TxConvBase58Addr(tx)) + res := jsonrpc.ConvTx(tx, jsonrpc.Base58) + cmd.Println(jsonrpc.MarshalJSON(res)) } else { txs := []*types.Tx{tx} var msgs *types.CommitResultList @@ -350,7 +351,8 @@ func runCallCmd(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to commit tx: %v", err.Error()) } - cmd.Println(util.JSON(msgs.Results[0])) + res := jsonrpc.ConvCommitResult(msgs.Results[0]) + cmd.Println(jsonrpc.MarshalJSON(res)) } return nil } @@ -366,7 +368,8 @@ func runGetABICmd(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to get abi: %v", err.Error()) } - cmd.Println(util.JSON(abi)) + res := jsonrpc.ConvAbi(abi) + cmd.Println(jsonrpc.MarshalJSON(res)) return nil } diff --git a/cmd/aergocli/cmd/enterprise.go b/cmd/aergocli/cmd/enterprise.go index ed57846d4..fdd5c80f1 100644 --- a/cmd/aergocli/cmd/enterprise.go +++ b/cmd/aergocli/cmd/enterprise.go @@ -8,17 +8,17 @@ package cmd import ( "context" "encoding/binary" + "encoding/json" "errors" "fmt" "strings" "time" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" "github.com/aergoio/aergo/v2/contract/enterprise" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -67,7 +67,7 @@ var enterpriseKeyCmd = &cobra.Command{ if strings.ToUpper(args[0]) != "PERMISSIONS" { out.On = &msg.On //it's for print false } - cmd.Println(util.B58JSON(out)) + cmd.Println(jsonrpc.MarshalJSON(out)) }, } @@ -86,7 +86,7 @@ func getConfChangeBlockNo(blockHash []byte) (aergorpc.BlockNo, error) { type OutConfChange struct { Payload string - TxStatus *aergorpc.EnterpriseTxStatus + TxStatus *jsonrpc.InOutEnterpriseTxStatus } func (occ *OutConfChange) ToString() string { @@ -222,21 +222,21 @@ var enterpriseTxCmd = &cobra.Command{ cmd.Println(output.ToString()) return } - output.TxStatus = &aergorpc.EnterpriseTxStatus{ + TxStatus := &aergorpc.EnterpriseTxStatus{ Status: receipt.GetStatus(), Ret: receipt.GetRet(), } if ci.Name == enterprise.ChangeCluster { if confChange, err = getChangeClusterStatus(cmd, msgblock.TxIdx.BlockHash, timer); err != nil { - output.TxStatus.CCStatus = &types.ChangeClusterStatus{Error: err.Error()} + TxStatus.CCStatus = &types.ChangeClusterStatus{Error: err.Error()} } if confChange != nil { - output.TxStatus.CCStatus = confChange.ToPrintable() + TxStatus.CCStatus = confChange.ToPrintable() } } - + output.TxStatus = jsonrpc.ConvEnterpriseTxStatus(TxStatus) cmd.Println(output.ToString()) } diff --git a/cmd/aergocli/cmd/event.go b/cmd/aergocli/cmd/event.go index 41d8dc2dd..4c50acaac 100644 --- a/cmd/aergocli/cmd/event.go +++ b/cmd/aergocli/cmd/event.go @@ -9,8 +9,8 @@ import ( "context" "log" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -82,7 +82,8 @@ func execListEvent(cmd *cobra.Command, args []string) { return } for _, event := range events.GetEvents() { - cmd.Println(util.JSON(event)) + res := jsonrpc.ConvEvent(event) + cmd.Println(jsonrpc.MarshalJSON(res)) } } @@ -108,6 +109,7 @@ func execStreamEvent(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s\n", err.Error()) return } - cmd.Println(util.JSON(event)) + res := jsonrpc.ConvEvent(event) + cmd.Println(jsonrpc.MarshalJSON(res)) } } diff --git a/cmd/aergocli/cmd/getblock.go b/cmd/aergocli/cmd/getblock.go index f54e43dbb..b3ac3c123 100644 --- a/cmd/aergocli/cmd/getblock.go +++ b/cmd/aergocli/cmd/getblock.go @@ -11,9 +11,9 @@ import ( "errors" "fmt" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -45,7 +45,8 @@ func streamBlocks(cmd *cobra.Command) error { if err != nil { return fmt.Errorf("failed to receive block: %v", err) } - cmd.Println(util.BlockConvBase58Addr(b)) + res := jsonrpc.ConvBlock(b) + cmd.Println(jsonrpc.MarshalJSON(res)) } } @@ -70,7 +71,8 @@ func getSingleBlock(cmd *cobra.Command) error { if err != nil { return fmt.Errorf("failed to get block: %v", err) } - cmd.Println(util.BlockConvBase58Addr(msg)) + res := jsonrpc.ConvBlock(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) return nil } diff --git a/cmd/aergocli/cmd/getpeers.go b/cmd/aergocli/cmd/getpeers.go index 571313eac..93d490a11 100644 --- a/cmd/aergocli/cmd/getpeers.go +++ b/cmd/aergocli/cmd/getpeers.go @@ -12,8 +12,8 @@ import ( "sort" "strings" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -59,12 +59,15 @@ func execGetPeers(cmd *cobra.Command, args []string) { // address and peerid should be encoded, respectively sorter.Sort(msg.Peers) if detailed == 0 { - cmd.Println(util.PeerListToString(msg)) + res := jsonrpc.ConvPeerList(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } else if detailed > 0 { // TODO show long fields - cmd.Println(util.LongPeerListToString(msg)) + res := jsonrpc.ConvLongPeerList(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } else { - cmd.Println(util.ShortPeerListToString(msg)) + res := jsonrpc.ConvShortPeerList(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } } diff --git a/cmd/aergocli/cmd/getstate.go b/cmd/aergocli/cmd/getstate.go index 7e3080523..e30f80533 100644 --- a/cmd/aergocli/cmd/getstate.go +++ b/cmd/aergocli/cmd/getstate.go @@ -8,9 +8,9 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -53,7 +53,7 @@ func execGetState(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s", err.Error()) return } - amount, err := util.ConvertUnit(msg.GetAmountBigInt(), unit) + amount, err := jsonrpc.ConvertUnit(msg.GetAmountBigInt(), unit) if err != nil { cmd.Printf("Failed: %s", err.Error()) return @@ -73,7 +73,7 @@ func execGetState(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s", err.Error()) return } - balance, err := util.ConvertUnit(msg.GetBalanceBigInt(), unit) + balance, err := jsonrpc.ConvertUnit(msg.GetBalanceBigInt(), unit) if err != nil { cmd.Printf("Failed: %s", err.Error()) return @@ -89,7 +89,7 @@ func execGetState(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s", err.Error()) return } - balance, err := util.ConvertUnit(msg.GetState().GetBalanceBigInt(), unit) + balance, err := jsonrpc.ConvertUnit(msg.GetState().GetBalanceBigInt(), unit) if err != nil { cmd.Printf("Failed: %s", err.Error()) return diff --git a/cmd/aergocli/cmd/gettx.go b/cmd/aergocli/cmd/gettx.go index a122aec0c..3791bcf34 100644 --- a/cmd/aergocli/cmd/gettx.go +++ b/cmd/aergocli/cmd/gettx.go @@ -8,9 +8,9 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -35,20 +35,20 @@ func execGetTX(cmd *cobra.Command, args []string) { cmd.Printf("Failed decode: %s", err.Error()) return } - payloadEncodingType := util.Base58 + payloadEncodingType := jsonrpc.Base58 if rawPayload { - payloadEncodingType = util.Raw + payloadEncodingType = jsonrpc.Raw } msg, err := client.GetTX(context.Background(), &aergorpc.SingleBytes{Value: txHash}) if err == nil { - cmd.Println(util.ConvTxEx(msg, payloadEncodingType)) + cmd.Println(jsonrpc.ConvTx(msg, payloadEncodingType)) } else { msgblock, err := client.GetBlockTX(context.Background(), &aergorpc.SingleBytes{Value: txHash}) if err != nil { cmd.Printf("Failed: %s", err.Error()) return } - cmd.Println(util.ConvTxInBlockEx(msgblock, payloadEncodingType)) + cmd.Println(jsonrpc.ConvTxInBlock(msgblock, payloadEncodingType)) } } diff --git a/cmd/aergocli/cmd/keygen.go b/cmd/aergocli/cmd/keygen.go index ef553d154..96b62e391 100644 --- a/cmd/aergocli/cmd/keygen.go +++ b/cmd/aergocli/cmd/keygen.go @@ -105,7 +105,7 @@ func generateKeyFiles(prefix string) error { pkFile := prefix + ".key" // Write private key file - err := saveKeyFile(pkFile, priv) + err := savePrivKeyFile(pkFile, priv) if err != nil { return err } @@ -117,7 +117,7 @@ func saveFilesFromKeys(priv crypto.PrivKey, pub crypto.PubKey, prefix string) er pubFile := prefix + ".pub" idFile := prefix + ".id" // Write public key file - err := saveKeyFile(pubFile, pub) + err := savePubKeyFile(pubFile, pub) if err != nil { return err } @@ -128,7 +128,7 @@ func saveFilesFromKeys(priv crypto.PrivKey, pub crypto.PubKey, prefix string) er saveBytesToFile(idFile, idBytes) if genAddress { - pkBytes, err := priv.Bytes() + pkBytes, err := crypto.MarshalPrivateKey(priv) if err != nil { return err } @@ -149,12 +149,28 @@ func saveFilesFromKeys(priv crypto.PrivKey, pub crypto.PubKey, prefix string) er return nil } -func saveKeyFile(pkFile string, priv crypto.Key) error { +func savePrivKeyFile(pkFile string, priv crypto.PrivKey) error { pkf, err := os.Create(pkFile) if err != nil { return err } - pkBytes, err := priv.Bytes() + pkBytes, err := crypto.MarshalPrivateKey(priv) + if err != nil { + return err + } + _, err = pkf.Write(pkBytes) + if err != nil { + return err + } + return pkf.Sync() +} + +func savePubKeyFile(pkFile string, pub crypto.PubKey) error { + pkf, err := os.Create(pkFile) + if err != nil { + return err + } + pkBytes, err := crypto.MarshalPublicKey(pub) if err != nil { return err } @@ -179,7 +195,7 @@ func saveBytesToFile(fileName string, bytes []byte) error { func generateKeyJson(priv crypto.PrivKey, pub crypto.PubKey) error { btcPK := p2putil.ConvertPKToBTCEC(priv) pkBytes := btcPK.Serialize() - pubBytes, err := pub.Bytes() + pubBytes, err := crypto.MarshalPublicKey(pub) pid, _ := types.IDFromPublicKey(pub) if err != nil { return err diff --git a/cmd/aergocli/cmd/listblocks.go b/cmd/aergocli/cmd/listblocks.go index aa4c1c003..e384a5ba6 100644 --- a/cmd/aergocli/cmd/listblocks.go +++ b/cmd/aergocli/cmd/listblocks.go @@ -8,9 +8,9 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -64,5 +64,6 @@ func execListBlockHeaders(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s", err.Error()) return } - cmd.Println(util.JSON(msg)) + res := jsonrpc.ConvBlockHeaderList(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } diff --git a/cmd/aergocli/cmd/mempool.go b/cmd/aergocli/cmd/mempool.go index 846d80a79..96643675d 100644 --- a/cmd/aergocli/cmd/mempool.go +++ b/cmd/aergocli/cmd/mempool.go @@ -6,7 +6,6 @@ import ( "log" "strings" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -77,6 +76,6 @@ func newAergoAdminClient(sockPath string) types.AdminRPCServiceClient { grpc.WithInsecure(), } return types.NewAdminRPCServiceClient( - util.GetConn(fmt.Sprintf("unix:%s", sockPath), opts), + GetConn(fmt.Sprintf("unix:%s", sockPath), opts), ) } diff --git a/cmd/aergocli/cmd/metric.go b/cmd/aergocli/cmd/metric.go index a18268a12..40e22a8e6 100644 --- a/cmd/aergocli/cmd/metric.go +++ b/cmd/aergocli/cmd/metric.go @@ -8,8 +8,8 @@ package cmd import ( "context" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -40,5 +40,6 @@ func execMetric(cmd *cobra.Command, args []string) { return } // address and peerid should be encoded, respectively - cmd.Println(util.JSON(msg)) + res := jsonrpc.ConvMetrics(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) } diff --git a/cmd/aergocli/cmd/name.go b/cmd/aergocli/cmd/name.go index 68b065ef1..82d4338ff 100644 --- a/cmd/aergocli/cmd/name.go +++ b/cmd/aergocli/cmd/name.go @@ -13,8 +13,8 @@ import ( "log" "math/big" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -77,7 +77,7 @@ func execNameNew(cmd *cobra.Command, args []string) error { if len(name) != types.NameLength { return errors.New("the name must be 12 alphabetic characters") } - amount, err := util.ParseUnit(spending) + amount, err := jsonrpc.ParseUnit(spending) if err != nil { return fmt.Errorf("wrong value in --amount flag: %v", err.Error()) } @@ -114,7 +114,7 @@ func execNameUpdate(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("Wrong address in --to flag: %v", err.Error()) } - amount, err := util.ParseUnit(spending) + amount, err := jsonrpc.ParseUnit(spending) if err != nil { return fmt.Errorf("Wrong value in --amount flag: %v", err.Error()) } diff --git a/cmd/aergocli/cmd/receipt.go b/cmd/aergocli/cmd/receipt.go index c36945556..3a033707c 100644 --- a/cmd/aergocli/cmd/receipt.go +++ b/cmd/aergocli/cmd/receipt.go @@ -9,9 +9,9 @@ import ( "context" "log" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" aergorpc "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -36,7 +36,8 @@ func init() { if err != nil { log.Fatal(err) } - cmd.Println(util.JSON(msg)) + res := jsonrpc.ConvReceipt(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) }, }, ) diff --git a/cmd/aergocli/cmd/root.go b/cmd/aergocli/cmd/root.go index 53d496d95..b1e4957a0 100644 --- a/cmd/aergocli/cmd/root.go +++ b/cmd/aergocli/cmd/root.go @@ -9,11 +9,9 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" "log" "os" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -143,7 +141,7 @@ func connectAergo(cmd *cobra.Command, args []string) { log.Fatal("wrong tls setting : ", err) } certPool := x509.NewCertPool() - ca, err := ioutil.ReadFile(rootConfig.TLS.CACert) + ca, err := os.ReadFile(rootConfig.TLS.CACert) if err != nil { log.Fatal("could not read server certification file : ", err) } @@ -160,7 +158,7 @@ func connectAergo(cmd *cobra.Command, args []string) { opts = append(opts, grpc.WithInsecure()) } var ok bool - client, ok = util.GetClient(serverAddr, opts).(*util.ConnClient) + client, ok = GetClient(serverAddr, opts).(*ConnClient) if !ok { log.Fatal("internal error. wrong RPC client type") } diff --git a/cmd/aergocli/cmd/root_test.go b/cmd/aergocli/cmd/root_test.go index 1e4af6798..a372794d1 100644 --- a/cmd/aergocli/cmd/root_test.go +++ b/cmd/aergocli/cmd/root_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/aergoio/aergo/v2/cmd/aergocli/cmd/mock_types" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/golang/mock/gomock" "github.com/spf13/cobra" ) @@ -14,7 +13,7 @@ func initMock(t *testing.T) *mock_types.MockAergoRPCServiceClient { test = true ctrl := gomock.NewController(t) mock := mock_types.NewMockAergoRPCServiceClient(ctrl) - mockClient := &util.ConnClient{ + mockClient := &ConnClient{ AergoRPCServiceClient: mock, } client = mockClient diff --git a/cmd/aergocli/cmd/sendtx.go b/cmd/aergocli/cmd/sendtx.go index 0b01d7df6..44fbfcaff 100644 --- a/cmd/aergocli/cmd/sendtx.go +++ b/cmd/aergocli/cmd/sendtx.go @@ -9,9 +9,9 @@ import ( "context" "errors" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -46,7 +46,7 @@ func execSendTX(cmd *cobra.Command, args []string) error { if err != nil { return errors.New("Wrong address in --to flag\n" + err.Error()) } - amountBigInt, err := util.ParseUnit(amount) + amountBigInt, err := jsonrpc.ParseUnit(amount) if err != nil { return errors.New("Wrong value in --amount flag\n" + err.Error()) } @@ -100,12 +100,14 @@ func sendTX(cmd *cobra.Command, tx *types.Tx, account []byte) string { if err != nil { return "Failed request to aergo server: " + err.Error() } - return util.JSON(msgs.Results[0]) + res := jsonrpc.ConvCommitResult(msgs.Results[0]) + return jsonrpc.MarshalJSON(res) } else { msg, err := client.SendTX(context.Background(), tx) if err != nil { return "Failed request to aergo sever: " + err.Error() } - return util.JSON(msg) + res := jsonrpc.ConvCommitResult(msg) + return jsonrpc.MarshalJSON(res) } } diff --git a/cmd/aergocli/cmd/sendtx_test.go b/cmd/aergocli/cmd/sendtx_test.go index cec44f02e..c5ef7574c 100644 --- a/cmd/aergocli/cmd/sendtx_test.go +++ b/cmd/aergocli/cmd/sendtx_test.go @@ -1,11 +1,12 @@ package cmd import ( + "encoding/json" "testing" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -32,9 +33,9 @@ func TestSendTxWithMock(t *testing.T) { output, err := executeCommand(rootCmd, "sendtx", "--from", "AmNL5neKQS2ZwRuBeqfcfHMLg3aSmGoefEh5bW8ozWxrtmxaGHZ3", "--to", "AmNfacq5A3orqn3MhgkHSncufXEP8gVJgqDy8jTgBphXQeuuaHHF", "--amount", "1000", "--keystore", "") assert.NoError(t, err, "should no error") t.Log(output) - out := &types.CommitResult{} + out := &jsonrpc.InOutCommitResult{} err = json.Unmarshal([]byte(output), out) - assert.Equal(t, testTxHashString, base58.Encode(out.Hash)) + assert.Equal(t, testTxHashString, out.Hash) } func TestSendTxFromToValidation(t *testing.T) { diff --git a/cmd/aergocli/cmd/signtx.go b/cmd/aergocli/cmd/signtx.go index 748784ff1..86b90bfc8 100644 --- a/cmd/aergocli/cmd/signtx.go +++ b/cmd/aergocli/cmd/signtx.go @@ -7,9 +7,9 @@ import ( "github.com/aergoio/aergo/v2/account/key" crypto "github.com/aergoio/aergo/v2/account/key/crypto" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/btcsuite/btcd/btcec" "github.com/spf13/cobra" ) @@ -36,7 +36,7 @@ var signCmd = &cobra.Command{ cmd.Printf("need to transaction json input") return } - param, err := util.ParseBase58TxBody([]byte(jsonTx)) + param, err := jsonrpc.ParseBase58TxBody([]byte(jsonTx)) if err != nil { cmd.Printf("Failed: %s\n", err.Error()) return @@ -89,7 +89,8 @@ var signCmd = &cobra.Command{ } if nil == err && msg != nil { - cmd.Println(util.TxConvBase58Addr(msg)) + res := jsonrpc.ConvTx(msg, jsonrpc.Base58) + cmd.Println(jsonrpc.MarshalJSON(res)) } else { cmd.Printf("Failed: %s\n", err.Error()) } @@ -105,7 +106,7 @@ var verifyCmd = &cobra.Command{ cmd.Printf("need to transaction json input") return } - param, err := util.ParseBase58Tx([]byte(jsonTx)) + param, err := jsonrpc.ParseBase58Tx([]byte(jsonTx)) if err != nil { cmd.Printf("Failed: %s\n", err.Error()) return @@ -117,7 +118,8 @@ var verifyCmd = &cobra.Command{ return } if msg.Tx != nil { - cmd.Println(util.TxConvBase58Addr(msg.Tx)) + res := jsonrpc.ConvTx(msg.Tx, jsonrpc.Base58) + cmd.Println(jsonrpc.MarshalJSON(res)) } else { cmd.Println(msg.Error) } @@ -127,7 +129,8 @@ var verifyCmd = &cobra.Command{ cmd.Printf("Failed: %s\n", err.Error()) return } - cmd.Println(util.TxConvBase58Addr(param[0])) + res := jsonrpc.ConvTx(param[0], jsonrpc.Base58) + cmd.Println(jsonrpc.MarshalJSON(res)) } }, } diff --git a/cmd/aergocli/cmd/signtx_test.go b/cmd/aergocli/cmd/signtx_test.go index 972aca344..109a9af38 100644 --- a/cmd/aergocli/cmd/signtx_test.go +++ b/cmd/aergocli/cmd/signtx_test.go @@ -1,15 +1,15 @@ package cmd import ( + "encoding/json" "os" "regexp" "strings" "testing" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" - "github.com/aergoio/aergo/v2/cmd/aergocli/util/encoding/json" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/stretchr/testify/assert" ) @@ -26,7 +26,7 @@ func TestSignWithKey(t *testing.T) { assert.Equalf(t, types.AddressLength, len(addr), "wrong address length value = %s", output) ouputjson := strings.Join(outputline[1:], "") - var tx util.InOutTx + var tx jsonrpc.InOutTx err = json.Unmarshal([]byte(ouputjson), &tx) assert.NoError(t, err, "should be success") diff --git a/cmd/aergocli/cmd/stake.go b/cmd/aergocli/cmd/stake.go index b4bc18038..0f843ef0a 100644 --- a/cmd/aergocli/cmd/stake.go +++ b/cmd/aergocli/cmd/stake.go @@ -9,8 +9,8 @@ import ( "encoding/json" "errors" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -47,7 +47,7 @@ func sendStake(cmd *cobra.Command, s bool) error { } else { ci.Name = types.Opunstake.Cmd() } - amountBigInt, err := util.ParseUnit(amount) + amountBigInt, err := jsonrpc.ParseUnit(amount) if err != nil { return errors.New("Failed to parse --amount flag\n" + err.Error()) } diff --git a/cmd/aergocli/cmd/vote.go b/cmd/aergocli/cmd/vote.go index 122c35441..09957d7c7 100644 --- a/cmd/aergocli/cmd/vote.go +++ b/cmd/aergocli/cmd/vote.go @@ -12,9 +12,9 @@ import ( "os" "strings" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -136,7 +136,8 @@ func execVoteStat(cmd *cobra.Command, args []string) { cmd.Printf("Failed: %s\n", err.Error()) return } - cmd.Println(util.JSON(msg)) + res := jsonrpc.ConvInOutAccountVoteInfo(msg) + cmd.Println(jsonrpc.MarshalJSON(res)) return } else if fflags.Changed("id") == true { msg, err := client.GetVotes(context.Background(), &types.VoteParams{ diff --git a/cmd/aergocli/util/base58addr.go b/cmd/aergocli/util/base58addr.go deleted file mode 100644 index c744d6591..000000000 --- a/cmd/aergocli/util/base58addr.go +++ /dev/null @@ -1,354 +0,0 @@ -package util - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "strconv" - "time" - - "github.com/aergoio/aergo/v2/internal/enc/base58" - "github.com/aergoio/aergo/v2/p2p/p2putil" - "github.com/aergoio/aergo/v2/types" -) - -type InOutBlockHeader struct { - ChainID string - Version int32 - PrevBlockHash string - BlockNo uint64 - Timestamp int64 - BlockRootHash string - TxRootHash string - ReceiptsRootHash string - Confirms uint64 - PubKey string - Sign string - CoinbaseAccount string - Consensus string -} - -type InOutBlockBody struct { - Txs []*InOutTx -} - -type InOutBlock struct { - Hash string - Header InOutBlockHeader - Body InOutBlockBody -} - -type InOutBlockIdx struct { - BlockHash string - BlockNo uint64 -} - -type InOutPeerAddress struct { - Address string - Port string - PeerId string -} - -type InOutPeer struct { - Role string - Address InOutPeerAddress - BestBlock InOutBlockIdx - LastCheck time.Time - State string - Hidden bool - Self bool - Version string -} - -type LongInOutPeer struct { - InOutPeer - ProducerIDs []string - Certificates []*InOutCert -} - -type InOutCert struct { - CertVersion uint32 - ProducerID string - CreateTime time.Time - ExpireTime time.Time - AgentID string - Addresses []string -} - -func FillTxBody(source *InOutTxBody, target *types.TxBody) error { - var err error - if source == nil { - return errors.New("tx body is empty") - } - target.Nonce = source.Nonce - if source.Account != "" { - target.Account, err = types.DecodeAddress(source.Account) - if err != nil { - return err - } - } - if source.Recipient != "" { - target.Recipient, err = types.DecodeAddress(source.Recipient) - if err != nil { - return err - } - } - if source.Amount != "" { - amount, err := ParseUnit(source.Amount) - if err != nil { - return err - } - target.Amount = amount.Bytes() - } - if source.Payload != "" { - target.Payload, err = base58.Decode(source.Payload) - if err != nil { - return err - } - } - target.GasLimit = source.GasLimit - if source.GasPrice != "" { - price, err := ParseUnit(source.GasPrice) - if err != nil { - return err - } - target.GasPrice = price.Bytes() - } - if source.ChainIdHash != "" { - target.ChainIdHash, err = base58.Decode(source.ChainIdHash) - if err != nil { - return err - } - } - if source.Sign != "" { - target.Sign, err = base58.Decode(source.Sign) - if err != nil { - return err - } - } - target.Type = source.Type - return nil -} - -func ParseBase58Tx(jsonTx []byte) ([]*types.Tx, error) { - var inputlist []InOutTx - err := json.Unmarshal([]byte(jsonTx), &inputlist) - if err != nil { - var input InOutTx - err = json.Unmarshal([]byte(jsonTx), &input) - if err != nil { - return nil, err - } - inputlist = append(inputlist, input) - } - txs := make([]*types.Tx, len(inputlist)) - for i, in := range inputlist { - tx := &types.Tx{Body: &types.TxBody{}} - if in.Hash != "" { - tx.Hash, err = base58.Decode(in.Hash) - if err != nil { - return nil, err - } - } - err = FillTxBody(in.Body, tx.Body) - if err != nil { - return nil, err - } - txs[i] = tx - } - - return txs, nil -} - -func ParseBase58TxBody(jsonTx []byte) (*types.TxBody, error) { - body := &types.TxBody{} - in := &InOutTxBody{} - - err := json.Unmarshal(jsonTx, in) - if err != nil { - return nil, err - } - - err = FillTxBody(in, body) - if err != nil { - return nil, err - } - - return body, nil -} - -func ConvTxEx(tx *types.Tx, payloadType EncodingType) *InOutTx { - out := &InOutTx{Body: &InOutTxBody{}} - if tx == nil { - return out - } - out.Hash = base58.Encode(tx.Hash) - out.Body.Nonce = tx.Body.Nonce - if tx.Body.Account != nil { - out.Body.Account = types.EncodeAddress(tx.Body.Account) - } - if tx.Body.Recipient != nil { - out.Body.Recipient = types.EncodeAddress(tx.Body.Recipient) - } - if tx.Body.Amount != nil { - out.Body.Amount = new(big.Int).SetBytes(tx.Body.Amount).String() - } - switch payloadType { - case Raw: - out.Body.Payload = string(tx.Body.Payload) - case Base58: - out.Body.Payload = base58.Encode(tx.Body.Payload) - } - out.Body.GasLimit = tx.Body.GasLimit - if tx.Body.GasPrice != nil { - out.Body.GasPrice = new(big.Int).SetBytes(tx.Body.GasPrice).String() - } - out.Body.ChainIdHash = base58.Encode(tx.Body.ChainIdHash) - out.Body.Sign = base58.Encode(tx.Body.Sign) - out.Body.Type = tx.Body.Type - return out -} - -func ConvTxInBlockEx(txInBlock *types.TxInBlock, payloadType EncodingType) *InOutTxInBlock { - out := &InOutTxInBlock{TxIdx: &InOutTxIdx{}, Tx: &InOutTx{}} - out.TxIdx.BlockHash = base58.Encode(txInBlock.GetTxIdx().GetBlockHash()) - out.TxIdx.Idx = txInBlock.GetTxIdx().GetIdx() - out.Tx = ConvTxEx(txInBlock.GetTx(), payloadType) - return out -} - -func ConvBlock(b *types.Block) *InOutBlock { - out := &InOutBlock{} - if b != nil { - out.Hash = base58.Encode(b.Hash) - out.Header.ChainID = base58.Encode(b.GetHeader().GetChainID()) - out.Header.Version = types.DecodeChainIdVersion(b.GetHeader().GetChainID()) - out.Header.PrevBlockHash = base58.Encode(b.GetHeader().GetPrevBlockHash()) - out.Header.BlockNo = b.GetHeader().GetBlockNo() - out.Header.Timestamp = b.GetHeader().GetTimestamp() - out.Header.BlockRootHash = base58.Encode(b.GetHeader().GetBlocksRootHash()) - out.Header.TxRootHash = base58.Encode(b.GetHeader().GetTxsRootHash()) - out.Header.ReceiptsRootHash = base58.Encode(b.GetHeader().GetReceiptsRootHash()) - out.Header.Confirms = b.GetHeader().GetConfirms() - out.Header.PubKey = base58.Encode(b.GetHeader().GetPubKey()) - out.Header.Sign = base58.Encode(b.GetHeader().GetSign()) - if b.GetHeader().GetCoinbaseAccount() != nil { - out.Header.CoinbaseAccount = types.EncodeAddress(b.GetHeader().GetCoinbaseAccount()) - } - if consensus := b.GetHeader().GetConsensus(); consensus != nil { - out.Header.Consensus = types.EncodeAddress(consensus) - } - - if b.Body != nil { - for _, tx := range b.Body.Txs { - out.Body.Txs = append(out.Body.Txs, ConvTx(tx)) - } - } - } - return out -} - -func ConvPeer(p *types.Peer) *InOutPeer { - out := &InOutPeer{} - out.Role = p.AcceptedRole.String() - out.Address.Address = p.GetAddress().GetAddress() - out.Address.Port = strconv.Itoa(int(p.GetAddress().GetPort())) - out.Address.PeerId = base58.Encode(p.GetAddress().GetPeerID()) - out.LastCheck = time.Unix(0, p.GetLashCheck()) - out.BestBlock.BlockNo = p.GetBestblock().GetBlockNo() - out.BestBlock.BlockHash = base58.Encode(p.GetBestblock().GetBlockHash()) - out.State = types.PeerState(p.State).String() - out.Hidden = p.Hidden - out.Self = p.Selfpeer - if p.Version != "" { - out.Version = p.Version - } else { - out.Version = "(old)" - } - return out -} - -func ConvPeerLong(p *types.Peer) *LongInOutPeer { - out := &LongInOutPeer{InOutPeer: *ConvPeer(p)} - out.ProducerIDs = make([]string, len(p.Address.ProducerIDs)) - for i, pid := range p.Address.ProducerIDs { - out.ProducerIDs[i] = base58.Encode(pid) - } - if p.Address.Role == types.PeerRole_Agent { - out.Certificates = make([]*InOutCert, len(p.Certificates)) - for i, cert := range p.Certificates { - addrs := []string{} - for _, ad := range cert.AgentAddress { - addrs = append(addrs, string(ad)) - } - out.Certificates[i] = &InOutCert{CertVersion: cert.CertVersion, - ProducerID: base58.Encode(cert.BPID), AgentID: base58.Encode(cert.AgentID), - CreateTime: time.Unix(0, cert.CreateTime), ExpireTime: time.Unix(0, cert.ExpireTime), - Addresses: addrs} - } - } - return out -} - -func ConvBlockchainStatus(in *types.BlockchainStatus) string { - out := &InOutBlockchainStatus{} - if in == nil { - return "" - } - out.Hash = base58.Encode(in.BestBlockHash) - out.Height = in.BestHeight - - out.ChainIdHash = base58.Encode(in.BestChainIdHash) - - toJRM := func(s string) *json.RawMessage { - if len(s) > 0 { - m := json.RawMessage(s) - return &m - } - return nil - } - out.ConsensusInfo = toJRM(in.ConsensusInfo) - if in.ChainInfo != nil { - out.ChainInfo = convChainInfo(in.ChainInfo) - } - jsonout, err := json.Marshal(out) - if err != nil { - return "" - } - return string(jsonout) -} - -func BlockConvBase58Addr(b *types.Block) string { - return toString(ConvBlock(b)) -} - -func PeerListToString(p *types.PeerList) string { - peers := []*InOutPeer{} - for _, peer := range p.GetPeers() { - peers = append(peers, ConvPeer(peer)) - } - return toString(peers) -} -func ShortPeerListToString(p *types.PeerList) string { - var peers []string - for _, peer := range p.GetPeers() { - pa := peer.Address - peers = append(peers, fmt.Sprintf("%s;%s/%d;%s;%d", p2putil.ShortForm(types.PeerID(pa.PeerID)), pa.Address, pa.Port, peer.AcceptedRole.String(), peer.Bestblock.BlockNo)) - } - return toString(peers) -} -func LongPeerListToString(p *types.PeerList) string { - peers := []*LongInOutPeer{} - for _, peer := range p.GetPeers() { - peers = append(peers, ConvPeerLong(peer)) - } - return toString(peers) -} -func toString(out interface{}) string { - jsonout, err := json.MarshalIndent(out, "", " ") - if err != nil { - return "" - } - return string(jsonout) -} diff --git a/cmd/aergocli/util/base58addr_test.go b/cmd/aergocli/util/base58addr_test.go deleted file mode 100644 index ec6800c28..000000000 --- a/cmd/aergocli/util/base58addr_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package util - -import ( - "testing" - - "github.com/aergoio/aergo/v2/internal/enc/base58" - "github.com/aergoio/aergo/v2/types" - "github.com/stretchr/testify/assert" -) - -func TestParseConvBase58Tx(t *testing.T) { - testjson := "[{\"Hash\":\"525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi\",\"Body\":{\"Nonce\":9,\"Account\":\"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd\",\"Recipient\":\"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR\",\"Amount\":\"100000000\",\"Payload\":null,\"Limit\":100,\"Price\":\"1\",\"Type\":0,\"Sign\":\"3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzLWh1SkkGYMGJ669nCVuYHrhwfg1HrUUp6KDwzK\"}}]" - res, err := ParseBase58Tx([]byte(testjson)) - assert.NoError(t, err, "should be success") - assert.NotEmpty(t, res, "failed to parse json") - assert.Equal(t, "525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi", base58.Encode(res[0].Hash), "wrong hash") - assert.Equal(t, "3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzLWh1SkkGYMGJ669nCVuYHrhwfg1HrUUp6KDwzK", base58.Encode(res[0].Body.Sign), "wrong sign") - - account, err := types.DecodeAddress("AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd") - assert.NoError(t, err, "should be success") - assert.Equal(t, account, res[0].Body.Account, "wrong account") - - recipient, err := types.DecodeAddress("AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR") - assert.NoError(t, err, "should be success") - assert.Equal(t, recipient, res[0].Body.Recipient, "wrong recipient") -} - -func TestParseBase58TxBody(t *testing.T) { - testjson := "{\"Nonce\":1,\"Account\":\"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd\",\"Recipient\":\"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR\",\"Amount\":\"25000\",\"Payload\":\"aergo\",\"Limit\":100,\"Price\":\"1\",\"Type\":0,\"Sign\":\"3roWPzztf5aLLh16vAnd2ugcPux3wJ1oqqvqkWARobjuAC32xftF42nnbTkXUQdkDaFvuUmctrpQSv8FAVUKcywHW\"}" - res, err := ParseBase58TxBody([]byte(testjson)) - assert.NoError(t, err, "should be success") - assert.NotEmpty(t, res, "failed to parse json") - - assert.Equal(t, "3roWPzztf5aLLh16vAnd2ugcPux3wJ1oqqvqkWARobjuAC32xftF42nnbTkXUQdkDaFvuUmctrpQSv8FAVUKcywHW", base58.Encode(res.Sign), "wrong sign") - assert.Equal(t, "aergo", base58.Encode(res.Payload), "wrong payload") - account, err := types.DecodeAddress("AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd") - assert.NoError(t, err, "should be success") - assert.Equal(t, account, res.Account, "wrong account") - - recipient, err := types.DecodeAddress("AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR") - assert.NoError(t, err, "should be success") - assert.Equal(t, recipient, res.Recipient, "wrong recipient") -} - -func TestBlockConvBase58(t *testing.T) { - const accountBase58 = "AmMW2bVcfroiuV4Bvy56op5zzqn42xgrLCwSxMka23K75yTBmudz" - const recipientBase58 = "AmMW2bVcfroiuV4Bvy56op5zzqn42xgrLCwSxMka23K75yTBmudz" - const payloadBase58 = "525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi" - - account, err := types.DecodeAddress(accountBase58) - assert.NoError(t, err, "should be decode account") - - testBlock := &types.Block{ - Header: &types.BlockHeader{ - CoinbaseAccount: account, - }, - Body: &types.BlockBody{ - Txs: []*types.Tx{}, - }, - } - result := ConvBlock(nil) - assert.Empty(t, result, "failed to convert nil") - - result = ConvBlock(testBlock) - assert.Empty(t, result.Body.Txs, "failed to convert txs") - assert.Equal(t, accountBase58, result.Header.CoinbaseAccount, "failed to convert coinbase account") - - recipient, err := types.DecodeAddress(recipientBase58) - assert.NoError(t, err, "should be decode recipient") - - payload, err := base58.Decode(payloadBase58) - assert.NoError(t, err, "should be decode payload") - - testTx := &types.Tx{Body: &types.TxBody{ - Account: account, - Recipient: recipient, - Payload: payload, - }} - - testBlock.Body.Txs = append(testBlock.Body.Txs, testTx) - result = ConvBlock(testBlock) - assert.Equal(t, accountBase58, result.Body.Txs[0].Body.Account, "failed to convert account") - assert.Equal(t, recipientBase58, result.Body.Txs[0].Body.Recipient, "failed to convert recipient") - assert.Equal(t, payloadBase58, result.Body.Txs[0].Body.Payload, "failed to convert payload") - t.Log(BlockConvBase58Addr(testBlock)) -} diff --git a/cmd/aergocli/util/base64ToHex.go b/cmd/aergocli/util/base64ToHex.go deleted file mode 100644 index 6ce5d33aa..000000000 --- a/cmd/aergocli/util/base64ToHex.go +++ /dev/null @@ -1,29 +0,0 @@ -package util - -import ( - "encoding/json" - - "github.com/aergoio/aergo/v2/internal/enc/hex" - "github.com/aergoio/aergo/v2/types" -) - -type InOutBlockchainStatus struct { - Hash string - Height uint64 - ConsensusInfo *json.RawMessage `json:",omitempty"` - ChainIdHash string - ChainStat *json.RawMessage `json:",omitempty"` - ChainInfo *InOutChainInfo `json:",omitempty"` -} - -func ConvHexBlockchainStatus(in *types.BlockchainStatus) string { - out := &InOutBlockchainStatus{} - out.Hash = hex.Encode(in.BestBlockHash) - out.Height = in.BestHeight - out.ChainIdHash = hex.Encode(in.BestChainIdHash) - jsonout, err := json.Marshal(out) - if err != nil { - return "" - } - return string(jsonout) -} diff --git a/cmd/aergocli/util/convChaininfo.go b/cmd/aergocli/util/convChaininfo.go deleted file mode 100644 index 0efa70662..000000000 --- a/cmd/aergocli/util/convChaininfo.go +++ /dev/null @@ -1,61 +0,0 @@ -package util - -import ( - "encoding/json" - "math/big" - - "github.com/aergoio/aergo/v2/consensus" - "github.com/aergoio/aergo/v2/types" -) - -type InOutChainId struct { - Magic string - Public bool - Mainnet bool - Consensus string - Version int32 -} - -type InOutChainInfo struct { - Chainid InOutChainId - BpNumber uint32 - MaxBlockSize uint64 - MaxTokens string - StakingMinimum string `json:",omitempty"` - StakingTotal string `json:",omitempty"` - GasPrice string `json:",omitempty"` - NamePrice string `json:",omitempty"` - TotalVotingPower string `json:",omitempty"` - VotingReward string `json:",omitempty"` -} - -func ConvChainInfoMsg(msg *types.ChainInfo) string { - jsonout, err := json.MarshalIndent(convChainInfo(msg), "", " ") - if err != nil { - return "" - } - return string(jsonout) -} - -func convChainInfo(msg *types.ChainInfo) *InOutChainInfo { - out := &InOutChainInfo{} - out.Chainid.Magic = msg.Id.Magic - out.Chainid.Public = msg.Id.Public - out.Chainid.Mainnet = msg.Id.Mainnet - out.Chainid.Consensus = msg.Id.Consensus - out.Chainid.Version = msg.Id.Version - out.BpNumber = msg.BpNumber - out.MaxBlockSize = msg.Maxblocksize - out.MaxTokens = new(big.Int).SetBytes(msg.Maxtokens).String() - - if consensus.IsDposName(msg.Id.Consensus) { - out.StakingMinimum = new(big.Int).SetBytes(msg.Stakingminimum).String() - out.StakingTotal = new(big.Int).SetBytes(msg.Totalstaking).String() - } - - out.GasPrice = new(big.Int).SetBytes(msg.Gasprice).String() - out.NamePrice = new(big.Int).SetBytes(msg.Nameprice).String() - out.TotalVotingPower = new(big.Int).SetBytes(msg.Totalvotingpower).String() - out.VotingReward = new(big.Int).SetBytes(msg.Votingreward).String() - return out -} diff --git a/cmd/aergocli/util/convTx.go b/cmd/aergocli/util/convTx.go deleted file mode 100644 index a3392d258..000000000 --- a/cmd/aergocli/util/convTx.go +++ /dev/null @@ -1,76 +0,0 @@ -package util - -import "github.com/aergoio/aergo/v2/types" - -type EncodingType int - -const ( - Raw EncodingType = 0 + iota - Base58 -) - -type InOutTx struct { - Hash string `json:",omitempty"` - Body *InOutTxBody `json:",omitempty"` -} - -type InOutTxBody struct { - Nonce uint64 `json:",omitempty"` - Account string `json:",omitempty"` - Recipient string `json:",omitempty"` - Amount string `json:",omitempty"` - Payload string `json:",omitempty"` - GasLimit uint64 `json:",omitempty"` - GasPrice string `json:",omitempty"` - Type types.TxType `json:",omitempty"` - ChainIdHash string `json:",omitempty"` - Sign string `json:",omitempty"` -} - -type InOutTxIdx struct { - BlockHash string - Idx int32 -} - -type InOutTxInBlock struct { - TxIdx *InOutTxIdx - Tx *InOutTx -} - -func (b *InOutTxBody) String() string { - return toString(b) -} - -func (t *InOutTx) String() string { - return toString(t) -} - -func (t *InOutTxInBlock) String() string { - return toString(t) -} - -func TxConvBase58Addr(tx *types.Tx) string { - return toString(ConvTx(tx)) -} - -func TxConvBase58AddrEx(tx *types.Tx, payloadType EncodingType) string { - switch payloadType { - case Raw: - return toString(ConvTxEx(tx, Raw)) - case Base58: - return toString(ConvTxEx(tx, Base58)) - } - return "" -} - -func TxInBlockConvBase58Addr(txInBlock *types.TxInBlock) string { - return toString(ConvTxInBlock(txInBlock)) -} - -func ConvTx(tx *types.Tx) *InOutTx { - return ConvTxEx(tx, Base58) -} - -func ConvTxInBlock(txInBlock *types.TxInBlock) *InOutTxInBlock { - return ConvTxInBlockEx(txInBlock, Base58) -} diff --git a/cmd/aergocli/util/convTx_test.go b/cmd/aergocli/util/convTx_test.go deleted file mode 100644 index 536c9950c..000000000 --- a/cmd/aergocli/util/convTx_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package util - -import ( - "testing" - - "github.com/aergoio/aergo/v2/types" - "github.com/stretchr/testify/assert" -) - -func TestConvTxEx(t *testing.T) { - testTx := &types.Tx{Body: &types.TxBody{Payload: []byte("{\"Name\":\"v1createName\",\"Args\":[\"honggildong3\"]}")}} - result := toString(ConvTxEx(testTx, Base58)) - assert.Equal(t, "{\n \"Body\": {\n \"Payload\": \"22MZAFWvxtVWehpgwEVxrvoqGL5xmcPmyLBiwraDfxRwKUNrV9tmhuB7Uu6ZeJWvp\"\n }\n}", result, "") - result = toString(ConvTxEx(testTx, Raw)) - assert.Equal(t, "{\n \"Body\": {\n \"Payload\": \"{\\\"Name\\\":\\\"v1createName\\\",\\\"Args\\\":[\\\"honggildong3\\\"]}\"\n }\n}", result, "") -} diff --git a/cmd/aergocli/util/encoding/encoding.go b/cmd/aergocli/util/encoding/encoding.go deleted file mode 100644 index cc5a53699..000000000 --- a/cmd/aergocli/util/encoding/encoding.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package encoding defines interfaces shared by other packages that -// convert data to and from byte-level and textual representations. -// Packages that check for these interfaces include encoding/gob, -// encoding/json, and encoding/xml. As a result, implementing an -// interface once can make a type useful in multiple encodings. -// Standard types that implement these interfaces include time.Time and net.IP. -// The interfaces come in pairs that produce and consume encoded data. -package encoding - -// BinaryMarshaler is the interface implemented by an object that can -// marshal itself into a binary form. -// -// MarshalBinary encodes the receiver into a binary form and returns the result. -type BinaryMarshaler interface { - MarshalBinary() (data []byte, err error) -} - -// BinaryUnmarshaler is the interface implemented by an object that can -// unmarshal a binary representation of itself. -// -// UnmarshalBinary must be able to decode the form generated by MarshalBinary. -// UnmarshalBinary must copy the data if it wishes to retain the data -// after returning. -type BinaryUnmarshaler interface { - UnmarshalBinary(data []byte) error -} - -// TextMarshaler is the interface implemented by an object that can -// marshal itself into a textual form. -// -// MarshalText encodes the receiver into UTF-8-encoded text and returns the result. -type TextMarshaler interface { - MarshalText() (text []byte, err error) -} - -// TextUnmarshaler is the interface implemented by an object that can -// unmarshal a textual representation of itself. -// -// UnmarshalText must be able to decode the form generated by MarshalText. -// UnmarshalText must copy the text if it wishes to retain the text -// after returning. -type TextUnmarshaler interface { - UnmarshalText(text []byte) error -} diff --git a/cmd/aergocli/util/encoding/json/decode.go b/cmd/aergocli/util/encoding/json/decode.go deleted file mode 100644 index a6dd2b0d8..000000000 --- a/cmd/aergocli/util/encoding/json/decode.go +++ /dev/null @@ -1,1289 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Represents JSON data structure using native Go types: booleans, floats, -// strings, arrays, and maps. - -package json - -import ( - "bytes" - "encoding" - "fmt" - "reflect" - "strconv" - "unicode" - "unicode/utf16" - "unicode/utf8" - - "github.com/aergoio/aergo/v2/internal/enc/base58" -) - -// Unmarshal parses the JSON-encoded data and stores the result -// in the value pointed to by v. If v is nil or not a pointer, -// Unmarshal returns an InvalidUnmarshalError. -// -// Unmarshal uses the inverse of the encodings that -// Marshal uses, allocating maps, slices, and pointers as necessary, -// with the following additional rules: -// -// To unmarshal JSON into a pointer, Unmarshal first handles the case of -// the JSON being the JSON literal null. In that case, Unmarshal sets -// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into -// the value pointed at by the pointer. If the pointer is nil, Unmarshal -// allocates a new value for it to point to. -// -// To unmarshal JSON into a value implementing the Unmarshaler interface, -// Unmarshal calls that value's UnmarshalJSON method, including -// when the input is a JSON null. -// Otherwise, if the value implements encoding.TextUnmarshaler -// and the input is a JSON quoted string, Unmarshal calls that value's -// UnmarshalText method with the unquoted form of the string. -// -// To unmarshal JSON into a struct, Unmarshal matches incoming object -// keys to the keys used by Marshal (either the struct field name or its tag), -// preferring an exact match but also accepting a case-insensitive match. By -// default, object keys which don't have a corresponding struct field are -// ignored (see Decoder.DisallowUnknownFields for an alternative). -// -// To unmarshal JSON into an interface value, -// Unmarshal stores one of these in the interface value: -// -// bool, for JSON booleans -// float64, for JSON numbers -// string, for JSON strings -// []interface{}, for JSON arrays -// map[string]interface{}, for JSON objects -// nil for JSON null -// -// To unmarshal a JSON array into a slice, Unmarshal resets the slice length -// to zero and then appends each element to the slice. -// As a special case, to unmarshal an empty JSON array into a slice, -// Unmarshal replaces the slice with a new empty slice. -// -// To unmarshal a JSON array into a Go array, Unmarshal decodes -// JSON array elements into corresponding Go array elements. -// If the Go array is smaller than the JSON array, -// the additional JSON array elements are discarded. -// If the JSON array is smaller than the Go array, -// the additional Go array elements are set to zero values. -// -// To unmarshal a JSON object into a map, Unmarshal first establishes a map to -// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal -// reuses the existing map, keeping existing entries. Unmarshal then stores -// key-value pairs from the JSON object into the map. The map's key type must -// either be a string, an integer, or implement encoding.TextUnmarshaler. -// -// If a JSON value is not appropriate for a given target type, -// or if a JSON number overflows the target type, Unmarshal -// skips that field and completes the unmarshaling as best it can. -// If no more serious errors are encountered, Unmarshal returns -// an UnmarshalTypeError describing the earliest such error. In any -// case, it's not guaranteed that all the remaining fields following -// the problematic one will be unmarshaled into the target object. -// -// The JSON null value unmarshals into an interface, map, pointer, or slice -// by setting that Go value to nil. Because null is often used in JSON to mean -// “not present,” unmarshaling a JSON null into any other Go type has no effect -// on the value and produces no error. -// -// When unmarshaling quoted strings, invalid UTF-8 or -// invalid UTF-16 surrogate pairs are not treated as an error. -// Instead, they are replaced by the Unicode replacement -// character U+FFFD. -func Unmarshal(data []byte, v interface{}) error { - // Check for well-formedness. - // Avoids filling out half a data structure - // before discovering a JSON syntax error. - var d decodeState - err := checkValid(data, &d.scan) - if err != nil { - return err - } - - d.init(data) - return d.unmarshal(v) -} - -// Unmarshaler is the interface implemented by types -// that can unmarshal a JSON description of themselves. -// The input can be assumed to be a valid encoding of -// a JSON value. UnmarshalJSON must copy the JSON data -// if it wishes to retain the data after returning. -// -// By convention, to approximate the behavior of Unmarshal itself, -// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. -type Unmarshaler interface { - UnmarshalJSON([]byte) error -} - -// An UnmarshalTypeError describes a JSON value that was -// not appropriate for a value of a specific Go type. -type UnmarshalTypeError struct { - Value string // description of JSON value - "bool", "array", "number -5" - Type reflect.Type // type of Go value it could not be assigned to - Offset int64 // error occurred after reading Offset bytes - Struct string // name of the struct type containing the field - Field string // name of the field holding the Go value -} - -func (e *UnmarshalTypeError) Error() string { - if e.Struct != "" || e.Field != "" { - return "json: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() - } - return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() -} - -// An UnmarshalFieldError describes a JSON object key that -// led to an unexported (and therefore unwritable) struct field. -// -// Deprecated: No longer used; kept for compatibility. -type UnmarshalFieldError struct { - Key string - Type reflect.Type - Field reflect.StructField -} - -func (e *UnmarshalFieldError) Error() string { - return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() -} - -// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. -// (The argument to Unmarshal must be a non-nil pointer.) -type InvalidUnmarshalError struct { - Type reflect.Type -} - -func (e *InvalidUnmarshalError) Error() string { - if e.Type == nil { - return "json: Unmarshal(nil)" - } - - if e.Type.Kind() != reflect.Ptr { - return "json: Unmarshal(non-pointer " + e.Type.String() + ")" - } - return "json: Unmarshal(nil " + e.Type.String() + ")" -} - -func (d *decodeState) unmarshal(v interface{}) error { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return &InvalidUnmarshalError{reflect.TypeOf(v)} - } - - d.scan.reset() - d.scanWhile(scanSkipSpace) - // We decode rv not rv.Elem because the Unmarshaler interface - // test must be applied at the top level of the value. - err := d.value(rv) - if err != nil { - return d.addErrorContext(err) - } - return d.savedError -} - -// A Number represents a JSON number literal. -type Number string - -// String returns the literal text of the number. -func (n Number) String() string { return string(n) } - -// Float64 returns the number as a float64. -func (n Number) Float64() (float64, error) { - return strconv.ParseFloat(string(n), 64) -} - -// Int64 returns the number as an int64. -func (n Number) Int64() (int64, error) { - return strconv.ParseInt(string(n), 10, 64) -} - -// isValidNumber reports whether s is a valid JSON number literal. -func isValidNumber(s string) bool { - // This function implements the JSON numbers grammar. - // See https://tools.ietf.org/html/rfc7159#section-6 - // and https://json.org/number.gif - - if s == "" { - return false - } - - // Optional - - if s[0] == '-' { - s = s[1:] - if s == "" { - return false - } - } - - // Digits - switch { - default: - return false - - case s[0] == '0': - s = s[1:] - - case '1' <= s[0] && s[0] <= '9': - s = s[1:] - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // . followed by 1 or more digits. - if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { - s = s[2:] - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // e or E followed by an optional - or + and - // 1 or more digits. - if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { - s = s[1:] - if s[0] == '+' || s[0] == '-' { - s = s[1:] - if s == "" { - return false - } - } - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // Make sure we are at the end. - return s == "" -} - -// decodeState represents the state while decoding a JSON value. -type decodeState struct { - data []byte - off int // next read offset in data - opcode int // last read result - scan scanner - errorContext struct { // provides context for type errors - Struct reflect.Type - Field string - } - savedError error - useNumber bool - disallowUnknownFields bool -} - -// readIndex returns the position of the last byte read. -func (d *decodeState) readIndex() int { - return d.off - 1 -} - -// phasePanicMsg is used as a panic message when we end up with something that -// shouldn't happen. It can indicate a bug in the JSON decoder, or that -// something is editing the data slice while the decoder executes. -const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?" - -func (d *decodeState) init(data []byte) *decodeState { - d.data = data - d.off = 0 - d.savedError = nil - d.errorContext.Struct = nil - d.errorContext.Field = "" - return d -} - -// saveError saves the first err it is called with, -// for reporting at the end of the unmarshal. -func (d *decodeState) saveError(err error) { - if d.savedError == nil { - d.savedError = d.addErrorContext(err) - } -} - -// addErrorContext returns a new error enhanced with information from d.errorContext -func (d *decodeState) addErrorContext(err error) error { - if d.errorContext.Struct != nil || d.errorContext.Field != "" { - switch err := err.(type) { - case *UnmarshalTypeError: - err.Struct = d.errorContext.Struct.Name() - err.Field = d.errorContext.Field - return err - } - } - return err -} - -// skip scans to the end of what was started. -func (d *decodeState) skip() { - s, data, i := &d.scan, d.data, d.off - depth := len(s.parseState) - for { - op := s.step(s, data[i]) - i++ - if len(s.parseState) < depth { - d.off = i - d.opcode = op - return - } - } -} - -// scanNext processes the byte at d.data[d.off]. -func (d *decodeState) scanNext() { - if d.off < len(d.data) { - d.opcode = d.scan.step(&d.scan, d.data[d.off]) - d.off++ - } else { - d.opcode = d.scan.eof() - d.off = len(d.data) + 1 // mark processed EOF with len+1 - } -} - -// scanWhile processes bytes in d.data[d.off:] until it -// receives a scan code not equal to op. -func (d *decodeState) scanWhile(op int) { - s, data, i := &d.scan, d.data, d.off - for i < len(data) { - newOp := s.step(s, data[i]) - i++ - if newOp != op { - d.opcode = newOp - d.off = i - return - } - } - - d.off = len(data) + 1 // mark processed EOF with len+1 - d.opcode = d.scan.eof() -} - -// value consumes a JSON value from d.data[d.off-1:], decoding into v, and -// reads the following byte ahead. If v is invalid, the value is discarded. -// The first byte of the value has been read already. -func (d *decodeState) value(v reflect.Value) error { - switch d.opcode { - default: - panic(phasePanicMsg) - - case scanBeginArray: - if v.IsValid() { - if err := d.array(v); err != nil { - return err - } - } else { - d.skip() - } - d.scanNext() - - case scanBeginObject: - if v.IsValid() { - if err := d.object(v); err != nil { - return err - } - } else { - d.skip() - } - d.scanNext() - - case scanBeginLiteral: - // All bytes inside literal return scanContinue op code. - start := d.readIndex() - d.scanWhile(scanContinue) - - if v.IsValid() { - if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil { - return err - } - } - } - return nil -} - -type unquotedValue struct{} - -// valueQuoted is like value but decodes a -// quoted string literal or literal null into an interface value. -// If it finds anything other than a quoted string literal or null, -// valueQuoted returns unquotedValue{}. -func (d *decodeState) valueQuoted() interface{} { - switch d.opcode { - default: - panic(phasePanicMsg) - - case scanBeginArray, scanBeginObject: - d.skip() - d.scanNext() - - case scanBeginLiteral: - v := d.literalInterface() - switch v.(type) { - case nil, string: - return v - } - } - return unquotedValue{} -} - -// indirect walks down v allocating pointers as needed, -// until it gets to a non-pointer. -// if it encounters an Unmarshaler, indirect stops and returns that. -// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. -func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { - // Issue #24153 indicates that it is generally not a guaranteed property - // that you may round-trip a reflect.Value by calling Value.Addr().Elem() - // and expect the value to still be settable for values derived from - // unexported embedded struct fields. - // - // The logic below effectively does this when it first addresses the value - // (to satisfy possible pointer methods) and continues to dereference - // subsequent pointers as necessary. - // - // After the first round-trip, we set v back to the original value to - // preserve the original RW flags contained in reflect.Value. - v0 := v - haveAddr := false - - // If v is a named type and is addressable, - // start with its address, so that if the type has pointer methods, - // we find them. - if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { - haveAddr = true - v = v.Addr() - } - for { - // Load value from interface, but only if the result will be - // usefully addressable. - if v.Kind() == reflect.Interface && !v.IsNil() { - e := v.Elem() - if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { - haveAddr = false - v = e - continue - } - } - - if v.Kind() != reflect.Ptr { - break - } - - if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { - break - } - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - if v.Type().NumMethod() > 0 { - if u, ok := v.Interface().(Unmarshaler); ok { - return u, nil, reflect.Value{} - } - if !decodingNull { - if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { - return nil, u, reflect.Value{} - } - } - } - - if haveAddr { - v = v0 // restore original value after round-trip Value.Addr().Elem() - haveAddr = false - } else { - v = v.Elem() - } - } - return nil, nil, v -} - -// array consumes an array from d.data[d.off-1:], decoding into v. -// The first byte of the array ('[') has been read already. -func (d *decodeState) array(v reflect.Value) error { - // Check for unmarshaler. - u, ut, pv := indirect(v, false) - if u != nil { - start := d.readIndex() - d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) - } - if ut != nil { - d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - } - v = pv - - // Check type of target. - switch v.Kind() { - case reflect.Interface: - if v.NumMethod() == 0 { - // Decoding into nil interface? Switch to non-reflect code. - ai := d.arrayInterface() - v.Set(reflect.ValueOf(ai)) - return nil - } - // Otherwise it's invalid. - fallthrough - default: - d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - case reflect.Array, reflect.Slice: - break - } - - i := 0 - for { - // Look ahead for ] - can only happen on first iteration. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndArray { - break - } - - // Get element of array, growing if necessary. - if v.Kind() == reflect.Slice { - // Grow slice if necessary - if i >= v.Cap() { - newcap := v.Cap() + v.Cap()/2 - if newcap < 4 { - newcap = 4 - } - newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) - reflect.Copy(newv, v) - v.Set(newv) - } - if i >= v.Len() { - v.SetLen(i + 1) - } - } - - if i < v.Len() { - // Decode into element. - if err := d.value(v.Index(i)); err != nil { - return err - } - } else { - // Ran out of fixed array: skip. - if err := d.value(reflect.Value{}); err != nil { - return err - } - } - i++ - - // Next token must be , or ]. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndArray { - break - } - if d.opcode != scanArrayValue { - panic(phasePanicMsg) - } - } - - if i < v.Len() { - if v.Kind() == reflect.Array { - // Array. Zero the rest. - z := reflect.Zero(v.Type().Elem()) - for ; i < v.Len(); i++ { - v.Index(i).Set(z) - } - } else { - v.SetLen(i) - } - } - if i == 0 && v.Kind() == reflect.Slice { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) - } - return nil -} - -var nullLiteral = []byte("null") -var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() - -// object consumes an object from d.data[d.off-1:], decoding into v. -// The first byte ('{') of the object has been read already. -func (d *decodeState) object(v reflect.Value) error { - // Check for unmarshaler. - u, ut, pv := indirect(v, false) - if u != nil { - start := d.readIndex() - d.skip() - return u.UnmarshalJSON(d.data[start:d.off]) - } - if ut != nil { - d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) - d.skip() - return nil - } - v = pv - t := v.Type() - - // Decoding into nil interface? Switch to non-reflect code. - if v.Kind() == reflect.Interface && v.NumMethod() == 0 { - oi := d.objectInterface() - v.Set(reflect.ValueOf(oi)) - return nil - } - - var fields []field - - // Check type of target: - // struct or - // map[T1]T2 where T1 is string, an integer type, - // or an encoding.TextUnmarshaler - switch v.Kind() { - case reflect.Map: - // Map key must either have string kind, have an integer kind, - // or be an encoding.TextUnmarshaler. - switch t.Key().Kind() { - case reflect.String, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - default: - if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { - d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) - d.skip() - return nil - } - } - if v.IsNil() { - v.Set(reflect.MakeMap(t)) - } - case reflect.Struct: - fields = cachedTypeFields(t) - // ok - default: - d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) - d.skip() - return nil - } - - var mapElem reflect.Value - originalErrorContext := d.errorContext - - for { - // Read opening " of string key or closing }. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if d.opcode != scanBeginLiteral { - panic(phasePanicMsg) - } - - // Read key. - start := d.readIndex() - d.scanWhile(scanContinue) - item := d.data[start:d.readIndex()] - key, ok := unquoteBytes(item) - if !ok { - panic(phasePanicMsg) - } - - // Figure out field corresponding to key. - var subv reflect.Value - destring := false // whether the value is wrapped in a string to be decoded first - - if v.Kind() == reflect.Map { - elemType := t.Elem() - if !mapElem.IsValid() { - mapElem = reflect.New(elemType).Elem() - } else { - mapElem.Set(reflect.Zero(elemType)) - } - subv = mapElem - } else { - var f *field - for i := range fields { - ff := &fields[i] - if bytes.Equal(ff.nameBytes, key) { - f = ff - break - } - if f == nil && ff.equalFold(ff.nameBytes, key) { - f = ff - } - } - if f != nil { - subv = v - destring = f.quoted - for _, i := range f.index { - if subv.Kind() == reflect.Ptr { - if subv.IsNil() { - // If a struct embeds a pointer to an unexported type, - // it is not possible to set a newly allocated value - // since the field is unexported. - // - // See https://golang.org/issue/21357 - if !subv.CanSet() { - d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem())) - // Invalidate subv to ensure d.value(subv) skips over - // the JSON value without assigning it to subv. - subv = reflect.Value{} - destring = false - break - } - subv.Set(reflect.New(subv.Type().Elem())) - } - subv = subv.Elem() - } - subv = subv.Field(i) - } - d.errorContext.Field = f.name - d.errorContext.Struct = t - } else if d.disallowUnknownFields { - d.saveError(fmt.Errorf("json: unknown field %q", key)) - } - } - - // Read : before value. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode != scanObjectKey { - panic(phasePanicMsg) - } - d.scanWhile(scanSkipSpace) - - if destring { - switch qv := d.valueQuoted().(type) { - case nil: - if err := d.literalStore(nullLiteral, subv, false); err != nil { - return err - } - case string: - if err := d.literalStore([]byte(qv), subv, true); err != nil { - return err - } - default: - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) - } - } else { - if err := d.value(subv); err != nil { - return err - } - } - - // Write value back to map; - // if using struct, subv points into struct already. - if v.Kind() == reflect.Map { - kt := t.Key() - var kv reflect.Value - switch { - case kt.Kind() == reflect.String: - kv = reflect.ValueOf(key).Convert(kt) - case reflect.PtrTo(kt).Implements(textUnmarshalerType): - kv = reflect.New(kt) - if err := d.literalStore(item, kv, true); err != nil { - return err - } - kv = kv.Elem() - default: - switch kt.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s := string(key) - n, err := strconv.ParseInt(s, 10, 64) - if err != nil || reflect.Zero(kt).OverflowInt(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - return nil - } - kv = reflect.ValueOf(n).Convert(kt) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s := string(key) - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || reflect.Zero(kt).OverflowUint(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: kt, Offset: int64(start + 1)}) - return nil - } - kv = reflect.ValueOf(n).Convert(kt) - default: - panic("json: Unexpected key type") // should never occur - } - } - v.SetMapIndex(kv, subv) - } - - // Next token must be , or }. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndObject { - break - } - if d.opcode != scanObjectValue { - panic(phasePanicMsg) - } - - d.errorContext = originalErrorContext - } - return nil -} - -// convertNumber converts the number literal s to a float64 or a Number -// depending on the setting of d.useNumber. -func (d *decodeState) convertNumber(s string) (interface{}, error) { - if d.useNumber { - return Number(s), nil - } - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return nil, &UnmarshalTypeError{Value: "number " + s, Type: reflect.TypeOf(0.0), Offset: int64(d.off)} - } - return f, nil -} - -var numberType = reflect.TypeOf(Number("")) - -// literalStore decodes a literal stored in item into v. -// -// fromQuoted indicates whether this literal came from unwrapping a -// string from the ",string" struct tag option. this is used only to -// produce more helpful error messages. -func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) error { - // Check for unmarshaler. - if len(item) == 0 { - //Empty string given - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - return nil - } - isNull := item[0] == 'n' // null - u, ut, pv := indirect(v, isNull) - if u != nil { - return u.UnmarshalJSON(item) - } - if ut != nil { - if item[0] != '"' { - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - return nil - } - val := "number" - switch item[0] { - case 'n': - val = "null" - case 't', 'f': - val = "bool" - } - d.saveError(&UnmarshalTypeError{Value: val, Type: v.Type(), Offset: int64(d.readIndex())}) - return nil - } - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - return ut.UnmarshalText(s) - } - - v = pv - - switch c := item[0]; c { - case 'n': // null - // The main parser checks that only true and false can reach here, - // but if this was a quoted string input, it could be anything. - if fromQuoted && string(item) != "null" { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - break - } - switch v.Kind() { - case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - v.Set(reflect.Zero(v.Type())) - // otherwise, ignore null for primitives/string - } - case 't', 'f': // true, false - value := item[0] == 't' - // The main parser checks that only true and false can reach here, - // but if this was a quoted string input, it could be anything. - if fromQuoted && string(item) != "true" && string(item) != "false" { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - break - } - switch v.Kind() { - default: - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) - } - case reflect.Bool: - v.SetBool(value) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(value)) - } else { - d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())}) - } - } - - case '"': // string - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - switch v.Kind() { - default: - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - case reflect.Slice: - if v.Type().Elem().Kind() != reflect.Uint8 { - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - b, err := base58.Decode(string(s)) - if err != nil { - d.saveError(err) - break - } - v.SetBytes(b) - case reflect.String: - v.SetString(string(s)) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(string(s))) - } else { - d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) - } - } - - default: // number - if c != '-' && (c < '0' || c > '9') { - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - panic(phasePanicMsg) - } - s := string(item) - switch v.Kind() { - default: - if v.Kind() == reflect.String && v.Type() == numberType { - v.SetString(s) - if !isValidNumber(s) { - return fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item) - } - break - } - if fromQuoted { - return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()) - } - d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) - case reflect.Interface: - n, err := d.convertNumber(s) - if err != nil { - d.saveError(err) - break - } - if v.NumMethod() != 0 { - d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.Set(reflect.ValueOf(n)) - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, err := strconv.ParseInt(s, 10, 64) - if err != nil || v.OverflowInt(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetInt(n) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || v.OverflowUint(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetUint(n) - - case reflect.Float32, reflect.Float64: - n, err := strconv.ParseFloat(s, v.Type().Bits()) - if err != nil || v.OverflowFloat(n) { - d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())}) - break - } - v.SetFloat(n) - } - } - return nil -} - -// The xxxInterface routines build up a value to be stored -// in an empty interface. They are not strictly necessary, -// but they avoid the weight of reflection in this common case. - -// valueInterface is like value but returns interface{} -func (d *decodeState) valueInterface() (val interface{}) { - switch d.opcode { - default: - panic(phasePanicMsg) - case scanBeginArray: - val = d.arrayInterface() - d.scanNext() - case scanBeginObject: - val = d.objectInterface() - d.scanNext() - case scanBeginLiteral: - val = d.literalInterface() - } - return -} - -// arrayInterface is like array but returns []interface{}. -func (d *decodeState) arrayInterface() []interface{} { - var v = make([]interface{}, 0) - for { - // Look ahead for ] - can only happen on first iteration. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndArray { - break - } - - v = append(v, d.valueInterface()) - - // Next token must be , or ]. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndArray { - break - } - if d.opcode != scanArrayValue { - panic(phasePanicMsg) - } - } - return v -} - -// objectInterface is like object but returns map[string]interface{}. -func (d *decodeState) objectInterface() map[string]interface{} { - m := make(map[string]interface{}) - for { - // Read opening " of string key or closing }. - d.scanWhile(scanSkipSpace) - if d.opcode == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if d.opcode != scanBeginLiteral { - panic(phasePanicMsg) - } - - // Read string key. - start := d.readIndex() - d.scanWhile(scanContinue) - item := d.data[start:d.readIndex()] - key, ok := unquote(item) - if !ok { - panic(phasePanicMsg) - } - - // Read : before value. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode != scanObjectKey { - panic(phasePanicMsg) - } - d.scanWhile(scanSkipSpace) - - // Read value. - m[key] = d.valueInterface() - - // Next token must be , or }. - if d.opcode == scanSkipSpace { - d.scanWhile(scanSkipSpace) - } - if d.opcode == scanEndObject { - break - } - if d.opcode != scanObjectValue { - panic(phasePanicMsg) - } - } - return m -} - -// literalInterface consumes and returns a literal from d.data[d.off-1:] and -// it reads the following byte ahead. The first byte of the literal has been -// read already (that's how the caller knows it's a literal). -func (d *decodeState) literalInterface() interface{} { - // All bytes inside literal return scanContinue op code. - start := d.readIndex() - d.scanWhile(scanContinue) - - item := d.data[start:d.readIndex()] - - switch c := item[0]; c { - case 'n': // null - return nil - - case 't', 'f': // true, false - return c == 't' - - case '"': // string - s, ok := unquote(item) - if !ok { - panic(phasePanicMsg) - } - return s - - default: // number - if c != '-' && (c < '0' || c > '9') { - panic(phasePanicMsg) - } - n, err := d.convertNumber(string(item)) - if err != nil { - d.saveError(err) - } - return n - } -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - var r rune - for _, c := range s[2:6] { - switch { - case '0' <= c && c <= '9': - c = c - '0' - case 'a' <= c && c <= 'f': - c = c - 'a' + 10 - case 'A' <= c && c <= 'F': - c = c - 'A' + 10 - default: - return -1 - } - r = r*16 + rune(c) - } - return r -} - -// unquote converts a quoted JSON string literal s into an actual string t. -// The rules are different than for Go, so cannot use strconv.Unquote. -func unquote(s []byte) (t string, ok bool) { - s, ok = unquoteBytes(s) - t = string(s) - return -} - -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError && size == 1 { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = unicode.ReplacementChar - } - w += utf8.EncodeRune(b[w:], rr) - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} diff --git a/cmd/aergocli/util/encoding/json/decode_test.go b/cmd/aergocli/util/encoding/json/decode_test.go deleted file mode 100644 index 1fb8c03eb..000000000 --- a/cmd/aergocli/util/encoding/json/decode_test.go +++ /dev/null @@ -1,2271 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package json - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "image" - "math" - "math/big" - "net" - "reflect" - "strconv" - "strings" - "testing" - "time" -) - -type T struct { - X string - Y int - Z int `json:"-"` -} - -type U struct { - Alphabet string `json:"alpha"` -} - -type V struct { - F1 interface{} - F2 int32 - F3 Number - F4 *VOuter -} - -type VOuter struct { - V V -} - -type W struct { - S SS -} - -type SS string - -func (*SS) UnmarshalJSON(data []byte) error { - return &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} -} - -// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and -// without UseNumber -var ifaceNumAsFloat64 = map[string]interface{}{ - "k1": float64(1), - "k2": "s", - "k3": []interface{}{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, -} - -var ifaceNumAsNumber = map[string]interface{}{ - "k1": Number("1"), - "k2": "s", - "k3": []interface{}{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]interface{}{"kk1": "s", "kk2": Number("2")}, -} - -type tx struct { - x int -} - -type u8 uint8 - -// A type that can unmarshal itself. - -type unmarshaler struct { - T bool -} - -func (u *unmarshaler) UnmarshalJSON(b []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. - return nil -} - -type ustruct struct { - M unmarshaler -} - -type unmarshalerText struct { - A, B string -} - -// needed for re-marshaling tests -func (u unmarshalerText) MarshalText() ([]byte, error) { - return []byte(u.A + ":" + u.B), nil -} - -func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.IndexByte(b, ':') - if pos == -1 { - return errors.New("missing separator") - } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) - return nil -} - -var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) - -type ustructText struct { - M unmarshalerText -} - -// u8marshal is an integer type that can marshal/unmarshal itself. -type u8marshal uint8 - -func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil -} - -var errMissingU8Prefix = errors.New("missing 'u' prefix") - -func (u8 *u8marshal) UnmarshalText(b []byte) error { - if !bytes.HasPrefix(b, []byte{'u'}) { - return errMissingU8Prefix - } - n, err := strconv.Atoi(string(b[1:])) - if err != nil { - return err - } - *u8 = u8marshal(n) - return nil -} - -var _ encoding.TextUnmarshaler = (*u8marshal)(nil) - -var ( - um0, um1 unmarshaler // target2 of unmarshaling - ump = &um1 - umtrue = unmarshaler{true} - umslice = []unmarshaler{{true}} - umslicep = new([]unmarshaler) - umstruct = ustruct{unmarshaler{true}} - - um0T, um1T unmarshalerText // target2 of unmarshaling - umpType = &um1T - umtrueXY = unmarshalerText{"x", "y"} - umsliceXY = []unmarshalerText{{"x", "y"}} - umslicepType = new([]unmarshalerText) - umstructType = new(ustructText) - umstructXY = ustructText{unmarshalerText{"x", "y"}} - - ummapType = map[unmarshalerText]bool{} - ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} -) - -// Test data structures for anonymous fields. - -type Point struct { - Z int -} - -type Top struct { - Level0 int - Embed0 - *Embed0a - *Embed0b `json:"e,omitempty"` // treated as named - Embed0c `json:"-"` // ignored - Loop - Embed0p // has Point with X, Y, used - Embed0q // has Point with Z, used - embed // contains exported field -} - -type Embed0 struct { - Level1a int // overridden by Embed0a's Level1a with json tag - Level1b int // used because Embed0a's Level1b is renamed - Level1c int // used because Embed0a's Level1c is ignored - Level1d int // annihilated by Embed0a's Level1d - Level1e int `json:"x"` // annihilated by Embed0a.Level1e -} - -type Embed0a struct { - Level1a int `json:"Level1a,omitempty"` - Level1b int `json:"LEVEL1B,omitempty"` - Level1c int `json:"-"` - Level1d int // annihilated by Embed0's Level1d - Level1f int `json:"x"` // annihilated by Embed0's Level1e -} - -type Embed0b Embed0 - -type Embed0c Embed0 - -type Embed0p struct { - image.Point -} - -type Embed0q struct { - Point -} - -type embed struct { - Q int -} - -type Loop struct { - Loop1 int `json:",omitempty"` - Loop2 int `json:",omitempty"` - *Loop -} - -// From reflect test: -// The X in S6 and S7 annihilate, but they also block the X in S8.S9. -type S5 struct { - S6 - S7 - S8 -} - -type S6 struct { - X int -} - -type S7 S6 - -type S8 struct { - S9 -} - -type S9 struct { - X int - Y int -} - -// From reflect test: -// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. -type S10 struct { - S11 - S12 - S13 -} - -type S11 struct { - S6 -} - -type S12 struct { - S6 -} - -type S13 struct { - S8 -} - -type Ambig struct { - // Given "hello", the first match should win. - First int `json:"HELLO"` - Second int `json:"Hello"` -} - -type XYZ struct { - X interface{} - Y interface{} - Z interface{} -} - -func sliceAddr(x []int) *[]int { return &x } -func mapAddr(x map[string]int) *map[string]int { return &x } - -type byteWithMarshalJSON byte - -func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil -} - -func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalJSON(i) - return nil -} - -type byteWithPtrMarshalJSON byte - -func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return byteWithMarshalJSON(*b).MarshalJSON() -} - -func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type byteWithMarshalText byte - -func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil -} - -func (b *byteWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalText(i) - return nil -} - -type byteWithPtrMarshalText byte - -func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { - return byteWithMarshalText(*b).MarshalText() -} - -func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*byteWithMarshalText)(b).UnmarshalText(data) -} - -type intWithMarshalJSON int - -func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil -} - -func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalJSON(i) - return nil -} - -type intWithPtrMarshalJSON int - -func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return intWithMarshalJSON(*b).MarshalJSON() -} - -func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*intWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type intWithMarshalText int - -func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil -} - -func (b *intWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalText(i) - return nil -} - -type intWithPtrMarshalText int - -func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { - return intWithMarshalText(*b).MarshalText() -} - -func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*intWithMarshalText)(b).UnmarshalText(data) -} - -type mapStringToStringData struct { - Data map[string]string `json:"data"` -} - -type unmarshalTest struct { - in string - ptr interface{} - out interface{} - err error - useNumber bool - golden bool - disallowUnknownFields bool -} - -type B struct { - B bool `json:",string"` -} - -var unmarshalTests = []unmarshalTest{ - // basic types - {in: `true`, ptr: new(bool), out: true}, - {in: `1`, ptr: new(int), out: 1}, - {in: `1.2`, ptr: new(float64), out: 1.2}, - {in: `-5`, ptr: new(int16), out: int16(-5)}, - {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, - {in: `2`, ptr: new(Number), out: Number("2")}, - {in: `2`, ptr: new(interface{}), out: float64(2.0)}, - {in: `2`, ptr: new(interface{}), out: Number("2"), useNumber: true}, - {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, - {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, - {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, - {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {in: "null", ptr: new(interface{}), out: nil}, - {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, - {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true}, - - // raw values with whitespace - {in: "\n true ", ptr: new(bool), out: true}, - {in: "\t 1 ", ptr: new(int), out: 1}, - {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, - - // Z has a "-" tag. - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - - // syntax errors - {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, - {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, - {in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, - {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}}, - - // raw value errors - {in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, - {in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, - {in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, - {in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, - - // array tests - {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, - - // empty array to interface test - {in: `[]`, ptr: new([]interface{}), out: []interface{}{}}, - {in: `null`, ptr: new([]interface{}), out: []interface{}(nil)}, - {in: `{"T":[]}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": []interface{}{}}}, - {in: `{"T":null}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": interface{}(nil)}}, - - // composite tests - {in: allValueIndent, ptr: new(All), out: allValue}, - {in: allValueCompact, ptr: new(All), out: allValue}, - {in: allValueIndent, ptr: new(*All), out: &allValue}, - {in: allValueCompact, ptr: new(*All), out: &allValue}, - {in: pallValueIndent, ptr: new(All), out: pallValue}, - {in: pallValueCompact, ptr: new(All), out: pallValue}, - {in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {in: pallValueCompact, ptr: new(*All), out: &pallValue}, - - // unmarshal interface test - {in: `{"T":false}`, ptr: &um0, out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {in: `{"T":false}`, ptr: &ump, out: &umtrue}, - {in: `[{"T":false}]`, ptr: &umslice, out: umslice}, - {in: `[{"T":false}]`, ptr: &umslicep, out: &umslice}, - {in: `{"M":{"T":"x:y"}}`, ptr: &umstruct, out: umstruct}, - - // UnmarshalText interface test - {in: `"x:y"`, ptr: &um0T, out: umtrueXY}, - {in: `"x:y"`, ptr: &umpType, out: &umtrueXY}, - {in: `["x:y"]`, ptr: &umsliceXY, out: umsliceXY}, - {in: `["x:y"]`, ptr: &umslicepType, out: &umsliceXY}, - {in: `{"M":"x:y"}`, ptr: umstructType, out: umstructXY}, - - // integer-keyed map test - { - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, - }, - { - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, - }, - { - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, - }, - { - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, - }, - { - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, - }, - - // Check that MarshalText and UnmarshalText take precedence - // over default integer handling in map keys. - { - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, - }, - { - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - err: errMissingU8Prefix, - }, - - // integer-keyed map errors - { - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, - }, - { - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - { - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, - }, - { - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - - // Map keys can be encoding.TextUnmarshalers. - {in: `{"x:y":true}`, ptr: &ummapType, out: ummapXY}, - // If multiple values for the same key exists, only the most recent value is used. - {in: `{"x:y":false,"x:y":true}`, ptr: &ummapType, out: ummapXY}, - - // Overwriting of data. - // This is different from package xml, but it's what we've always done. - // Now documented and tested. - {in: `[2]`, ptr: sliceAddr([]int{1}), out: []int{2}}, - {in: `{"key": 2}`, ptr: mapAddr(map[string]int{"old": 0, "key": 1}), out: map[string]int{"key": 2}}, - - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - }, - }, - { - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, - }, - - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - - // invalid UTF-8 is coerced to valid UTF-8. - { - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", - }, - - // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: &map[time.Time]string{}, - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, - }, - - // issue 8305 - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: &map[Point]string{}, - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, - }, - { - in: `{"asdf": "hello world"}`, - ptr: &map[unmarshaler]string{}, - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, - }, - - // related to issue 13783. - // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, - // similar to marshaling a slice of typed int. - // These tests check that, assuming the byte type also has valid decoding methods, - // either the old base64 string encoding or the new per-element encoding can be - // successfully unmarshaled. The custom unmarshalers were accessible in earlier - // versions of Go, even though the custom marshaler was not. - // - // TODO: fix to base58 - //{ - // in: `"AQID"`, - // ptr: new([]byteWithMarshalJSON), - // out: []byteWithMarshalJSON{1, 2, 3}, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]byteWithMarshalJSON), - // out: []byteWithMarshalJSON{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `"AQID"`, - // ptr: new([]byteWithMarshalText), - // out: []byteWithMarshalText{1, 2, 3}, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]byteWithMarshalText), - // out: []byteWithMarshalText{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `"AQID"`, - // ptr: new([]byteWithPtrMarshalJSON), - // out: []byteWithPtrMarshalJSON{1, 2, 3}, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]byteWithPtrMarshalJSON), - // out: []byteWithPtrMarshalJSON{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `"AQID"`, - // ptr: new([]byteWithPtrMarshalText), - // out: []byteWithPtrMarshalText{1, 2, 3}, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]byteWithPtrMarshalText), - // out: []byteWithPtrMarshalText{1, 2, 3}, - // golden: true, - //}, - // - // ints work with the marshaler but not the base64 []byte case - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]intWithMarshalJSON), - // out: []intWithMarshalJSON{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]intWithMarshalText), - // out: []intWithMarshalText{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]intWithPtrMarshalJSON), - // out: []intWithPtrMarshalJSON{1, 2, 3}, - // golden: true, - //}, - //{ - // in: `["Z01","Z02","Z03"]`, - // ptr: new([]intWithPtrMarshalText), - // out: []intWithPtrMarshalText{1, 2, 3}, - // golden: true, - //}, - - {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, - {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, - {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, - - { - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "F2", - Type: reflect.TypeOf(int32(0)), - Offset: 20, - }, - }, - { - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "F2", - Type: reflect.TypeOf(int32(0)), - Offset: 30, - }, - }, - - // issue 15146. - // invalid inputs in wrongStringTests below. - {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, - {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, - {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, - {in: `{"B": "null"}`, ptr: new(B), out: B{false}}, - {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, - {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, - - // additional tests for disallowUnknownFields - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18, - "extra": true - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12, - "extra": null - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - // issue 26444 - // UnmarshalTypeError without field & struct values - { - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, - }, - { - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, - }, - - // trying to decode JSON arrays or objects via TextUnmarshaler - { - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, - { - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, -} - -func TestMarshal(t *testing.T) { - b, err := Marshal(allValue) - if err != nil { - t.Fatalf("Marshal allValue: %v", err) - } - if string(b) != allValueCompact { - t.Errorf("Marshal allValueCompact") - diff(t, b, []byte(allValueCompact)) - return - } - - b, err = Marshal(pallValue) - if err != nil { - t.Fatalf("Marshal pallValue: %v", err) - } - if string(b) != pallValueCompact { - t.Errorf("Marshal pallValueCompact") - diff(t, b, []byte(pallValueCompact)) - return - } -} - -var badUTF8 = []struct { - in, out string -}{ - {"hello\xffworld", `"hello\ufffdworld"`}, - {"", `""`}, - {"\xff", `"\ufffd"`}, - {"\xff\xff", `"\ufffd\ufffd"`}, - {"a\xffb", `"a\ufffdb"`}, - {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, -} - -func TestMarshalBadUTF8(t *testing.T) { - for _, tt := range badUTF8 { - b, err := Marshal(tt.in) - if string(b) != tt.out || err != nil { - t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out) - } - } -} - -func TestMarshalNumberZeroVal(t *testing.T) { - var n Number - out, err := Marshal(n) - if err != nil { - t.Fatal(err) - } - outStr := string(out) - if outStr != "0" { - t.Fatalf("Invalid zero val for Number: %q", outStr) - } -} - -func TestMarshalEmbeds(t *testing.T) { - top := &Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - } - b, err := Marshal(top) - if err != nil { - t.Fatal(err) - } - want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(b) != want { - t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) - } -} - -func TestUnmarshal(t *testing.T) { - for i, tt := range unmarshalTests { - var scan scanner - in := []byte(tt.in) - if err := checkValid(in, &scan); err != nil { - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: checkValid: %#v", i, err) - continue - } - } - if tt.ptr == nil { - continue - } - - // v = new(right-type) - v := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec := NewDecoder(bytes.NewReader(in)) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if err := dec.Decode(v.Interface()); !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - continue - } else if err != nil { - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out) - data, _ := Marshal(v.Elem().Interface()) - println(string(data)) - data, _ = Marshal(tt.out) - println(string(data)) - continue - } - - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Errorf("#%d: error re-marshaling: %v", i, err) - continue - } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in) - } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) - if tt.useNumber { - dec.UseNumber() - } - if err := dec.Decode(vv.Interface()); err != nil { - t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) - t.Errorf(" In: %q", strings.Map(noSpace, string(in))) - t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) - continue - } - } - } -} - -func TestUnmarshalMarshal(t *testing.T) { - initBig() - var v interface{} - if err := Unmarshal(jsonBig, &v); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - b, err := Marshal(v) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal jsonBig") - diff(t, b, jsonBig) - return - } -} - -var numberTests = []struct { - in string - i int64 - intErr string - f float64 - floatErr string -}{ - {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {in: "-12", i: -12, f: -12.0}, - {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, -} - -// Independent of Decode, basic coverage of the accessors in Number -func TestNumberAccessors(t *testing.T) { - for _, tt := range numberTests { - n := Number(tt.in) - if s := n.String(); s != tt.in { - t.Errorf("Number(%q).String() is %q", tt.in, s) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("Number(%q).Int64() is %d", tt.in, i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("Number(%q).Float64() is %g", tt.in, f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) - } - } -} - -func TestLargeByteSlice(t *testing.T) { - s0 := make([]byte, 2000) - for i := range s0 { - s0[i] = byte(i) - } - b, err := Marshal(s0) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - var s1 []byte - if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !bytes.Equal(s0, s1) { - t.Errorf("Marshal large byte slice") - diff(t, s0, s1) - } -} - -type Xint struct { - X int -} - -func TestUnmarshalInterface(t *testing.T) { - var xint Xint - var i interface{} = &xint - if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("Did not write to xint") - } -} - -func TestUnmarshalPtrPtr(t *testing.T) { - var xint Xint - pxint := &xint - if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("Did not write to xint") - } -} - -func TestEscape(t *testing.T) { - const input = `"foobar"` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - b, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if s := string(b); s != expected { - t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) - } -} - -// WrongString is a struct that's misusing the ,string modifier. -type WrongString struct { - Message string `json:"result,string"` -} - -type wrongStringTest struct { - in, err string -} - -var wrongStringTests = []wrongStringTest{ - {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, - {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, - {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, - {`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, -} - -// If people misuse the ,string modifier, the error message should be -// helpful, telling the user that they're doing it wrong. -func TestErrorMessageFromMisusedString(t *testing.T) { - for n, tt := range wrongStringTests { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if got != tt.err { - t.Errorf("%d. got err = %q, want %q", n, got, tt.err) - } - } -} - -func noSpace(c rune) rune { - if isSpace(byte(c)) { //only used for ascii - return -1 - } - return c -} - -type All struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Uintptr uintptr - Float32 float32 - Float64 float64 - - Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` - - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - - PBool *bool - PInt *int - PInt8 *int8 - PInt16 *int16 - PInt32 *int32 - PInt64 *int64 - PUint *uint - PUint8 *uint8 - PUint16 *uint16 - PUint32 *uint32 - PUint64 *uint64 - PUintptr *uintptr - PFloat32 *float32 - PFloat64 *float64 - - String string - PString *string - - Map map[string]Small - MapP map[string]*Small - PMap *map[string]Small - PMapP *map[string]*Small - - EmptyMap map[string]Small - NilMap map[string]Small - - Slice []Small - SliceP []*Small - PSlice *[]Small - PSliceP *[]*Small - - EmptySlice []Small - NilSlice []Small - - StringSlice []string - ByteSlice []byte - - Small Small - PSmall *Small - PPSmall **Small - - Interface interface{} - PInterface *interface{} - - unexported int -} - -type Small struct { - Tag string -} - -var allValue = All{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Uintptr: 12, - Float32: 14.1, - Float64: 15.1, - Foo: "foo", - Foo2: "foo2", - IntStr: 42, - UintptrStr: 44, - String: "16", - Map: map[string]Small{ - "17": {Tag: "tag17"}, - "18": {Tag: "tag18"}, - }, - MapP: map[string]*Small{ - "19": {Tag: "tag19"}, - "20": nil, - }, - EmptyMap: map[string]Small{}, - Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, - SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, - EmptySlice: []Small{}, - StringSlice: []string{"str24", "str25", "str26"}, - ByteSlice: []byte{27, 28, 29}, - Small: Small{Tag: "tag30"}, - PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, -} - -var pallValue = All{ - PBool: &allValue.Bool, - PInt: &allValue.Int, - PInt8: &allValue.Int8, - PInt16: &allValue.Int16, - PInt32: &allValue.Int32, - PInt64: &allValue.Int64, - PUint: &allValue.Uint, - PUint8: &allValue.Uint8, - PUint16: &allValue.Uint16, - PUint32: &allValue.Uint32, - PUint64: &allValue.Uint64, - PUintptr: &allValue.Uintptr, - PFloat32: &allValue.Float32, - PFloat64: &allValue.Float64, - PString: &allValue.String, - PMap: &allValue.Map, - PMapP: &allValue.MapP, - PSlice: &allValue.Slice, - PSliceP: &allValue.SliceP, - PPSmall: &allValue.PSmall, - PInterface: &allValue.Interface, -} - -var allValueIndent = `{ - "Bool": true, - "Int": 2, - "Int8": 3, - "Int16": 4, - "Int32": 5, - "Int64": 6, - "Uint": 7, - "Uint8": 8, - "Uint16": 9, - "Uint32": 10, - "Uint64": 11, - "Uintptr": 12, - "Float32": 14.1, - "Float64": 15.1, - "bar": "foo", - "bar2": "foo2", - "IntStr": "42", - "UintptrStr": "44", - "PBool": null, - "PInt": null, - "PInt8": null, - "PInt16": null, - "PInt32": null, - "PInt64": null, - "PUint": null, - "PUint8": null, - "PUint16": null, - "PUint32": null, - "PUint64": null, - "PUintptr": null, - "PFloat32": null, - "PFloat64": null, - "String": "16", - "PString": null, - "Map": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "MapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "PMap": null, - "PMapP": null, - "EmptyMap": {}, - "NilMap": null, - "Slice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "SliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "PSlice": null, - "PSliceP": null, - "EmptySlice": [], - "NilSlice": null, - "StringSlice": [ - "str24", - "str25", - "str26" - ], - "ByteSlice": "A79E", - "Small": { - "Tag": "tag30" - }, - "PSmall": { - "Tag": "tag31" - }, - "PPSmall": null, - "Interface": 5.2, - "PInterface": null -}` - -var allValueCompact = strings.Map(noSpace, allValueIndent) - -var pallValueIndent = `{ - "Bool": false, - "Int": 0, - "Int8": 0, - "Int16": 0, - "Int32": 0, - "Int64": 0, - "Uint": 0, - "Uint8": 0, - "Uint16": 0, - "Uint32": 0, - "Uint64": 0, - "Uintptr": 0, - "Float32": 0, - "Float64": 0, - "bar": "", - "bar2": "", - "IntStr": "0", - "UintptrStr": "0", - "PBool": true, - "PInt": 2, - "PInt8": 3, - "PInt16": 4, - "PInt32": 5, - "PInt64": 6, - "PUint": 7, - "PUint8": 8, - "PUint16": 9, - "PUint32": 10, - "PUint64": 11, - "PUintptr": 12, - "PFloat32": 14.1, - "PFloat64": 15.1, - "String": "", - "PString": "16", - "Map": null, - "MapP": null, - "PMap": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "PMapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "EmptyMap": null, - "NilMap": null, - "Slice": null, - "SliceP": null, - "PSlice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "PSliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "EmptySlice": null, - "NilSlice": null, - "StringSlice": null, - "ByteSlice": null, - "Small": { - "Tag": "" - }, - "PSmall": null, - "PPSmall": { - "Tag": "tag31" - }, - "Interface": null, - "PInterface": 5.2 -}` - -var pallValueCompact = strings.Map(noSpace, pallValueIndent) - -func TestRefUnmarshal(t *testing.T) { - type S struct { - // Ref is defined in encode_test.go. - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - } - want := S{ - R0: 12, - R1: new(Ref), - R2: 13, - R3: new(RefText), - } - *want.R1 = 12 - *want.R3 = 13 - - var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %+v, want %+v", got, want) - } -} - -// Test that the empty string doesn't panic decoding when ,string is specified -// Issue 3450 -func TestEmptyString(t *testing.T) { - type T2 struct { - Number1 int `json:",string"` - Number2 int `json:",string"` - } - data := `{"Number1":"1", "Number2":""}` - dec := NewDecoder(strings.NewReader(data)) - var t2 T2 - err := dec.Decode(&t2) - if err == nil { - t.Fatal("Decode: did not return error") - } - if t2.Number1 != 1 { - t.Fatal("Decode: did not set Number1") - } -} - -// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). -// It should also not be an error (issue 2540, issue 8587). -func TestNullString(t *testing.T) { - type T struct { - A int `json:",string"` - B int `json:",string"` - C *int `json:",string"` - } - data := []byte(`{"A": "1", "B": null, "C": null}`) - var s T - s.B = 1 - s.C = new(int) - *s.C = 2 - err := Unmarshal(data, &s) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if s.B != 1 || s.C != nil { - t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) - } -} - -func intp(x int) *int { - p := new(int) - *p = x - return p -} - -func intpp(x *int) **int { - pp := new(*int) - *pp = x - return pp -} - -var interfaceSetTests = []struct { - pre interface{} - json string - post interface{} -}{ - {"foo", `"bar"`, "bar"}, - {"foo", `2`, 2.0}, - {"foo", `true`, true}, - {"foo", `null`, nil}, - - {nil, `null`, nil}, - {new(int), `null`, nil}, - {(*int)(nil), `null`, nil}, - {new(*int), `null`, new(*int)}, - {(**int)(nil), `null`, nil}, - {intp(1), `null`, nil}, - {intpp(nil), `null`, intpp(nil)}, - {intpp(intp(1)), `null`, intpp(nil)}, -} - -func TestInterfaceSet(t *testing.T) { - for _, tt := range interfaceSetTests { - b := struct{ X interface{} }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - t.Errorf("Unmarshal %#q: %v", blob, err) - continue - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("Unmarshal %#q into %#v: X=%#v, want %#v", blob, tt.pre, b.X, tt.post) - } - } -} - -type NullTest struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - PBool *bool - Map map[string]string - Slice []string - Interface interface{} - - PRaw *RawMessage - PTime *time.Time - PBigInt *big.Int - PText *MustNotUnmarshalText - PBuffer *bytes.Buffer // has methods, just not relevant ones - PStruct *struct{} - - Raw RawMessage - Time time.Time - BigInt big.Int - Text MustNotUnmarshalText - Buffer bytes.Buffer - Struct struct{} -} - -type NullTestStrings struct { - Bool bool `json:",string"` - Int int `json:",string"` - Int8 int8 `json:",string"` - Int16 int16 `json:",string"` - Int32 int32 `json:",string"` - Int64 int64 `json:",string"` - Uint uint `json:",string"` - Uint8 uint8 `json:",string"` - Uint16 uint16 `json:",string"` - Uint32 uint32 `json:",string"` - Uint64 uint64 `json:",string"` - Float32 float32 `json:",string"` - Float64 float64 `json:",string"` - String string `json:",string"` - PBool *bool `json:",string"` - Map map[string]string `json:",string"` - Slice []string `json:",string"` - Interface interface{} `json:",string"` - - PRaw *RawMessage `json:",string"` - PTime *time.Time `json:",string"` - PBigInt *big.Int `json:",string"` - PText *MustNotUnmarshalText `json:",string"` - PBuffer *bytes.Buffer `json:",string"` - PStruct *struct{} `json:",string"` - - Raw RawMessage `json:",string"` - Time time.Time `json:",string"` - BigInt big.Int `json:",string"` - Text MustNotUnmarshalText `json:",string"` - Buffer bytes.Buffer `json:",string"` - Struct struct{} `json:",string"` -} - -// JSON null values should be ignored for primitives and string values instead of resulting in an error. -// Issue 2540 -func TestUnmarshalNulls(t *testing.T) { - // Unmarshal docs: - // The JSON null value unmarshals into an interface, map, pointer, or slice - // by setting that Go value to nil. Because null is often used in JSON to mean - // ``not present,'' unmarshaling a JSON null into any other Go type has no effect - // on the value and produces no error. - - jsonData := []byte(`{ - "Bool" : null, - "Int" : null, - "Int8" : null, - "Int16" : null, - "Int32" : null, - "Int64" : null, - "Uint" : null, - "Uint8" : null, - "Uint16" : null, - "Uint32" : null, - "Uint64" : null, - "Float32" : null, - "Float64" : null, - "String" : null, - "PBool": null, - "Map": null, - "Slice": null, - "Interface": null, - "PRaw": null, - "PTime": null, - "PBigInt": null, - "PText": null, - "PBuffer": null, - "PStruct": null, - "Raw": null, - "Time": null, - "BigInt": null, - "Text": null, - "Buffer": null, - "Struct": null - }`) - nulls := NullTest{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Float32: 12.1, - Float64: 13.1, - String: "14", - PBool: new(bool), - Map: map[string]string{}, - Slice: []string{}, - Interface: new(MustNotUnmarshalJSON), - PRaw: new(RawMessage), - PTime: new(time.Time), - PBigInt: new(big.Int), - PText: new(MustNotUnmarshalText), - PStruct: new(struct{}), - PBuffer: new(bytes.Buffer), - Raw: RawMessage("123"), - Time: time.Unix(123456789, 0), - BigInt: *big.NewInt(123), - } - - before := nulls.Time.String() - - err := Unmarshal(jsonData, &nulls) - if err != nil { - t.Errorf("Unmarshal of null values failed: %v", err) - } - if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || - nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { - t.Errorf("Unmarshal of null values affected primitives") - } - - if nulls.PBool != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBool") - } - if nulls.Map != nil { - t.Errorf("Unmarshal of null did not clear nulls.Map") - } - if nulls.Slice != nil { - t.Errorf("Unmarshal of null did not clear nulls.Slice") - } - if nulls.Interface != nil { - t.Errorf("Unmarshal of null did not clear nulls.Interface") - } - if nulls.PRaw != nil { - t.Errorf("Unmarshal of null did not clear nulls.PRaw") - } - if nulls.PTime != nil { - t.Errorf("Unmarshal of null did not clear nulls.PTime") - } - if nulls.PBigInt != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBigInt") - } - if nulls.PText != nil { - t.Errorf("Unmarshal of null did not clear nulls.PText") - } - if nulls.PBuffer != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBuffer") - } - if nulls.PStruct != nil { - t.Errorf("Unmarshal of null did not clear nulls.PStruct") - } - - if string(nulls.Raw) != "null" { - t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) - } - if nulls.Time.String() != before { - t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) - } - if nulls.BigInt.String() != "123" { - t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) - } -} - -type MustNotUnmarshalJSON struct{} - -func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { - return errors.New("MustNotUnmarshalJSON was used") -} - -type MustNotUnmarshalText struct{} - -func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { - return errors.New("MustNotUnmarshalText was used") -} - -func TestStringKind(t *testing.T) { - type stringKind string - - var m1, m2 map[stringKind]int - m1 = map[stringKind]int{ - "foo": 42, - } - - data, err := Marshal(m1) - if err != nil { - t.Errorf("Unexpected error marshaling: %v", err) - } - - err = Unmarshal(data, &m2) - if err != nil { - t.Errorf("Unexpected error unmarshaling: %v", err) - } - - if !reflect.DeepEqual(m1, m2) { - t.Error("Items should be equal after encoding and then decoding") - } -} - -// Custom types with []byte as underlying type could not be marshaled -// and then unmarshaled. -// Issue 8962. -func TestByteKind(t *testing.T) { - type byteKind []byte - - a := byteKind("hello") - - data, err := Marshal(a) - if err != nil { - t.Error(err) - } - var b byteKind - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Errorf("expected %v == %v", a, b) - } -} - -// The fix for issue 8962 introduced a regression. -// Issue 12921. -func TestSliceOfCustomByte(t *testing.T) { - type Uint8 uint8 - - a := []Uint8("hello") - - data, err := Marshal(a) - if err != nil { - t.Fatal(err) - } - var b []Uint8 - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Fatalf("expected %v == %v", a, b) - } -} - -var decodeTypeErrorTests = []struct { - dest interface{} - src string -}{ - {new(string), `{"user": "name"}`}, // issue 4628. - {new(error), `{}`}, // issue 4222 - {new(error), `[]`}, - {new(error), `""`}, - {new(error), `123`}, - {new(error), `true`}, -} - -func TestUnmarshalTypeError(t *testing.T) { - for _, item := range decodeTypeErrorTests { - err := Unmarshal([]byte(item.src), item.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { - t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", - item.src, item.dest, err) - } - } -} - -var unmarshalSyntaxTests = []string{ - "tru", - "fals", - "nul", - "123e", - `"hello`, - `[1,2,3`, - `{"key":1`, - `{"key":1,`, -} - -func TestUnmarshalSyntax(t *testing.T) { - var x interface{} - for _, src := range unmarshalSyntaxTests { - err := Unmarshal([]byte(src), &x) - if _, ok := err.(*SyntaxError); !ok { - t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) - } - } -} - -// Test handling of unexported fields that should be ignored. -// Issue 4660 -type unexportedFields struct { - Name string - m map[string]interface{} `json:"-"` - m2 map[string]interface{} `json:"abcd"` - - s []int `json:"-"` -} - -func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} - - out := &unexportedFields{} - err := Unmarshal([]byte(input), out) - if err != nil { - t.Errorf("got error %v, expected nil", err) - } - if !reflect.DeepEqual(out, want) { - t.Errorf("got %q, want %q", out, want) - } -} - -// Time3339 is a time.Time which encodes to and from JSON -// as an RFC 3339 time in UTC. -type Time3339 time.Time - -func (t *Time3339) UnmarshalJSON(b []byte) error { - if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) - } - tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) - if err != nil { - return err - } - *t = Time3339(tm) - return nil -} - -func TestUnmarshalJSONLiteralError(t *testing.T) { - var t3 Time3339 - err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) - if err == nil { - t.Fatalf("expected error; got time %v", time.Time(t3)) - } - if !strings.Contains(err.Error(), "range") { - t.Errorf("got err = %v; want out of range error", err) - } -} - -// Test that extra object elements in an array do not result in a -// "data changing underfoot" error. -// Issue 3717 -func TestSkipArrayObjects(t *testing.T) { - json := `[{}]` - var dest [0]interface{} - - err := Unmarshal([]byte(json), &dest) - if err != nil { - t.Errorf("got error %q, want nil", err) - } -} - -// Test semantics of pre-filled struct fields and pre-filled map fields. -// Issue 4900. -func TestPrefilled(t *testing.T) { - ptrToMap := func(m map[string]interface{}) *map[string]interface{} { return &m } - - // Values here change, cannot reuse table across runs. - var prefillTests = []struct { - in string - ptr interface{} - out interface{} - }{ - { - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, - { - in: `{"X": 1, "Y": 2}`, - ptr: ptrToMap(map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}), - out: ptrToMap(map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}), - }, - } - - for _, tt := range prefillTests { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) - } - } -} - -var invalidUnmarshalTests = []struct { - v interface{} - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, -} - -func TestInvalidUnmarshal(t *testing.T) { - buf := []byte(`{"a":"1"}`) - for _, tt := range invalidUnmarshalTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } - } -} - -var invalidUnmarshalTextTests = []struct { - v interface{} - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, -} - -func TestInvalidUnmarshalText(t *testing.T) { - buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } - } -} - -// Test that string option is ignored for invalid types. -// Issue 9812. -func TestInvalidStringOption(t *testing.T) { - num := 0 - item := struct { - T time.Time `json:",string"` - M map[string]string `json:",string"` - S []string `json:",string"` - A [1]string `json:",string"` - I interface{} `json:",string"` - P *int `json:",string"` - }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} - - data, err := Marshal(item) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - - err = Unmarshal(data, &item) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } -} - -// Test unmarshal behavior with regards to embedded unexported structs. -// -// (Issue 21357) If the embedded struct is a pointer and is unallocated, -// this returns an error because unmarshal cannot set the field. -// -// (Issue 24152) If the embedded struct is given an explicit name, -// ensure that the normal unmarshal logic does not panic in reflect. -func TestUnmarshalEmbeddedUnexported(t *testing.T) { - type ( - embed1 struct{ Q int } - embed2 struct{ Q int } - embed3 struct { - Q int64 `json:",string"` - } - S1 struct { - *embed1 - R int - } - S2 struct { - *embed1 - Q int - } - S3 struct { - embed1 - R int - } - S4 struct { - *embed1 - embed2 - } - S5 struct { - *embed3 - R int - } - S6 struct { - embed1 `json:"embed1"` - } - S7 struct { - embed1 `json:"embed1"` - embed2 - } - S8 struct { - embed1 `json:"embed1"` - embed2 `json:"embed2"` - Q int - } - ) - - tests := []struct { - in string - ptr interface{} - out interface{} - err error - }{{ - // Error since we cannot set S1.embed1, but still able to set S1.R. - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), - }, { - // The top level Q field takes precedence. - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, - }, { - // No issue with non-pointer variant. - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, - }, { - // No error since both embedded structs have field R, which annihilate each other. - // Thus, no attempt is made at setting S4.embed1. - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), - }, { - // Error since we cannot set S5.embed1, but still able to set S5.R. - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), - }, { - // Issue 24152, ensure decodeState.indirect does not panic. - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, - }, { - // Issue 24153, check that we can still set forwarded fields even in - // the presence of a name conflict. - // - // This relies on obscure behavior of reflect where it is possible - // to set a forwarded exported field on an unexported embedded struct - // even though there is a name conflict, even when it would have been - // impossible to do so according to Go visibility rules. - // Go forbids this because it is ambiguous whether S7.Q refers to - // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, - // it should be impossible for an external package to set either Q. - // - // It is probably okay for a future reflect change to break this. - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, - }, { - // Issue 24153, similar to the S7 case. - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, - }} - - for i, tt := range tests { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("#%d: mismatch\ngot: %#+v\nwant: %#+v", i, tt.ptr, tt.out) - } - } -} - -type unmarshalPanic struct{} - -func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } - -func TestUnmarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - Unmarshal([]byte("{}"), &unmarshalPanic{}) - t.Fatalf("Unmarshal should have panicked") -} diff --git a/cmd/aergocli/util/encoding/json/encode.go b/cmd/aergocli/util/encoding/json/encode.go deleted file mode 100644 index 26b0c472a..000000000 --- a/cmd/aergocli/util/encoding/json/encode.go +++ /dev/null @@ -1,1253 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package json implements encoding and decoding of JSON as defined in -// RFC 7159. The mapping between JSON and Go values is described -// in the documentation for the Marshal and Unmarshal functions. -// -// See "JSON and Go" for an introduction to this package: -// https://golang.org/doc/articles/json_and_go.html -package json - -import ( - "bytes" - "encoding" - "fmt" - "math" - "reflect" - "sort" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" - - "github.com/aergoio/aergo/v2/internal/enc/base58" -) - -// Marshal returns the JSON encoding of v. -// -// Marshal traverses the value v recursively. -// If an encountered value implements the Marshaler interface -// and is not a nil pointer, Marshal calls its MarshalJSON method -// to produce JSON. If no MarshalJSON method is present but the -// value implements encoding.TextMarshaler instead, Marshal calls -// its MarshalText method and encodes the result as a JSON string. -// The nil pointer exception is not strictly necessary -// but mimics a similar, necessary exception in the behavior of -// UnmarshalJSON. -// -// Otherwise, Marshal uses the following type-dependent default encodings: -// -// Boolean values encode as JSON booleans. -// -// Floating point, integer, and Number values encode as JSON numbers. -// -// String values encode as JSON strings coerced to valid UTF-8, -// replacing invalid bytes with the Unicode replacement rune. -// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" -// to keep some browsers from misinterpreting JSON output as HTML. -// Ampersand "&" is also escaped to "\u0026" for the same reason. -// This escaping can be disabled using an Encoder that had SetEscapeHTML(false) -// called on it. -// -// Array and slice values encode as JSON arrays, except that -// []byte encodes as a base64-encoded string, and a nil slice -// encodes as the null JSON value. -// -// Struct values encode as JSON objects. -// Each exported struct field becomes a member of the object, using the -// field name as the object key, unless the field is omitted for one of the -// reasons given below. -// -// The encoding of each struct field can be customized by the format string -// stored under the "json" key in the struct field's tag. -// The format string gives the name of the field, possibly followed by a -// comma-separated list of options. The name may be empty in order to -// specify options without overriding the default field name. -// -// The "omitempty" option specifies that the field should be omitted -// from the encoding if the field has an empty value, defined as -// false, 0, a nil pointer, a nil interface value, and any empty array, -// slice, map, or string. -// -// As a special case, if the field tag is "-", the field is always omitted. -// Note that a field with name "-" can still be generated using the tag "-,". -// -// Examples of struct field tags and their meanings: -// -// // Field appears in JSON as key "myName". -// Field int `json:"myName"` -// -// // Field appears in JSON as key "myName" and -// // the field is omitted from the object if its value is empty, -// // as defined above. -// Field int `json:"myName,omitempty"` -// -// // Field appears in JSON as key "Field" (the default), but -// // the field is skipped if empty. -// // Note the leading comma. -// Field int `json:",omitempty"` -// -// // Field is ignored by this package. -// Field int `json:"-"` -// -// // Field appears in JSON as key "-". -// Field int `json:"-,"` -// -// The "string" option signals that a field is stored as JSON inside a -// JSON-encoded string. It applies only to fields of string, floating point, -// integer, or boolean types. This extra level of encoding is sometimes used -// when communicating with JavaScript programs: -// -// Int64String int64 `json:",string"` -// -// The key name will be used if it's a non-empty string consisting of -// only Unicode letters, digits, and ASCII punctuation except quotation -// marks, backslash, and comma. -// -// Anonymous struct fields are usually marshaled as if their inner exported fields -// were fields in the outer struct, subject to the usual Go visibility rules amended -// as described in the next paragraph. -// An anonymous struct field with a name given in its JSON tag is treated as -// having that name, rather than being anonymous. -// An anonymous struct field of interface type is treated the same as having -// that type as its name, rather than being anonymous. -// -// The Go visibility rules for struct fields are amended for JSON when -// deciding which field to marshal or unmarshal. If there are -// multiple fields at the same level, and that level is the least -// nested (and would therefore be the nesting level selected by the -// usual Go rules), the following extra rules apply: -// -// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, -// even if there are multiple untagged fields that would otherwise conflict. -// -// 2) If there is exactly one field (tagged or not according to the first rule), that is selected. -// -// 3) Otherwise there are multiple fields, and all are ignored; no error occurs. -// -// Handling of anonymous struct fields is new in Go 1.1. -// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of -// an anonymous struct field in both current and earlier versions, give the field -// a JSON tag of "-". -// -// Map values encode as JSON objects. The map's key type must either be a -// string, an integer type, or implement encoding.TextMarshaler. The map keys -// are sorted and used as JSON object keys by applying the following rules, -// subject to the UTF-8 coercion described for string values above: -// - string keys are used directly -// - encoding.TextMarshalers are marshaled -// - integer keys are converted to strings -// -// Pointer values encode as the value pointed to. -// A nil pointer encodes as the null JSON value. -// -// Interface values encode as the value contained in the interface. -// A nil interface value encodes as the null JSON value. -// -// Channel, complex, and function values cannot be encoded in JSON. -// Attempting to encode such a value causes Marshal to return -// an UnsupportedTypeError. -// -// JSON cannot represent cyclic data structures and Marshal does not -// handle them. Passing cyclic structures to Marshal will result in -// an infinite recursion. -func Marshal(v interface{}) ([]byte, error) { - e := newEncodeState() - - err := e.marshal(v, encOpts{escapeHTML: true}) - if err != nil { - return nil, err - } - buf := append([]byte(nil), e.Bytes()...) - - e.Reset() - encodeStatePool.Put(e) - - return buf, nil -} - -// MarshalIndent is like Marshal but applies Indent to format the output. -// Each JSON element in the output will begin on a new line beginning with prefix -// followed by one or more copies of indent according to the indentation nesting. -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { - b, err := Marshal(v) - if err != nil { - return nil, err - } - var buf bytes.Buffer - err = Indent(&buf, b, prefix, indent) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 -// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 -// so that the JSON will be safe to embed inside HTML + + + + diff --git a/rpc/swagger/swagger.yaml b/rpc/swagger/swagger.yaml new file mode 100644 index 000000000..19fa2775e --- /dev/null +++ b/rpc/swagger/swagger.yaml @@ -0,0 +1,1207 @@ +openapi: 3.0.0 +info: + title: Aergo Web3 + description: Aergo Web3 API + version: 0.1.0 +servers: + - url: http://localhost/v1 +tags: + - name: Account + description: Blockchain related operations + - name: Block + description: Block related operations + - name: Transaction + description: Transaction related operations + - name: Etc + description: Etc related operations +paths: + /getState: + get: + summary: Get account state + tags: [Account] + parameters: + - name: account + in: query + required: true + schema: + type: string + example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + responses: + "200": + description: Successful response + content: + application/json: + example: + nonce: 7, + balance: "28085280000000000000000" + account: "AmNpHWATDRqrK6YrF9shuPYpFwucX8dVGhW7X8ypmwBLSAoM6xDG" + stake: "10000000000000000000000" + total: "38085280000000000000000" + votingpower: "50000000000000000000000" + when: 255439 + nextaction: 341839 + isContract: false + /getStateAndProof: + get: + summary: Get account state and proof + tags: [Account] + parameters: + - name: account + in: query + required: true + schema: + type: string + example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + - name: compressed + in: query + required: true + schema: + type: boolean + example: true + responses: + "200": + description: Successful response + content: + application/json: + example: + account: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + nonce: 0 + balance: "100000000 aergo" + merkle proof length: 4 + /getNameInfo: + get: + summary: Owner of account name + tags: [Account] + parameters: + - name: name + in: query + schema: + type: string + example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + - name: number + in: query + schema: + type: int + responses: + "200": + description: Successful response + content: + application/json: + example: + name: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + owner: "" + destination: "" + /getBalance: + get: + summary: Get account balance + tags: [Account] + parameters: + - name: account + required: true + in: query + schema: + type: string + example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + responses: + "200": + description: Successful response + content: + application/json: + example: + balance: "1000000000000000000" + /getBlock: + get: + summary: Get block information + tags: [Block] + parameters: + - name: hash + in: query + schema: + type: string + - name: number + in: query + schema: + type: integer + example: 1 + responses: + "200": + description: Successful response + content: + application/json: + example: + hash: "C0pxUo0dljwDELyLG9lijEdmBjOOH9XNsfPfGMQ0pmA=" + header: + chainID: "AwAAAAAAYm10LmFlcmdvLmlvL3JhZnQ=" + prevBlockHash: "lK4dq/zpT/Y8cSSdebJY9j8FmGJHjKG4SJF4r4++a8E=" + blockNo: 1 + timestamp: 1692154145436345000 + blocksRootHash: "4YQSYp0dkqUHYzTLAciMW9ZDc3RU89VMYuFBpfyHxaQ=" + txsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + receiptsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + pubKey: "CAISIQNmmKv+inhXk+dWY2XdX8MxkZk7SCxlBoXSrAaYKsweFQ==" + sign: "MEQCIAu7xj6QIBKM+IfSLQho+b1l3fHXhxWYbpxC9ZnfLT2pAiAEbSmG7oeRGSfkWW96xAuaAZri9tlx4LD/eZNS9mQzlw==" + body: {} + /blockchain: + get: + summary: Get current blockchain status + tags: [Block] + responses: + "200": + description: Successful response + content: + application/json: + example: + best_block_hash: "OxnydGsA8B0VFcKOM09cq3qOlyZjkr10Zz9yUYR0eWg=" + best_height: 491 + consensus_info: + Type: "raft" + Status: + Leader: "local1" + Total: 3 + Name: "local1" + RaftId: "6d3862958785a554" + Status: null + best_chain_id_hash: "mhY5TnCy9Uv4X9mQCk0ad0jdUqGMrX05oixwJBvQnH0=" + chain_info: + id: + magic: "bmt.aergo.io" + consensus: "raft" + version: 3 + bpNumber: 3 + maxblocksize: 1000400 + maxtokens: "AkMGxAl4WcQ8AAAA" + gasprice: "C6Q7dAA=" + nameprice: "DeC2s6dkAAA=" + /getBlockTx: + get: + summary: Get block body + tags: [Block] + parameters: + - name: hash + in: query + schema: + type: string + example: "" + - name: number + in: query + require: true + schema: + type: integer + example: 1 + - name: offset + in: query + require: true + schema: + type: integer + example: 0 + - name: size + in: query + require: true + schema: + type: integer + example: 100 + responses: + "200": + description: Successful response + content: + application/json: + example: + offset: 0 + size: 100 + txs: + [ + { + "hash": "7dYEViBVX3Qs1BTTPntT8fvi1zsBUFGbFtUscioWxQWJ", + "body": + { + "nonce": 6, + "account": "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R", + "recipient": "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + "amount": "1000000000000000000", + "type": 4, + "chainIdHash": "BNVSYKKqSR78hTSrxkaroFK2S3mgsoocpWg9HYtb1Zhn", + "sign": "AN1rKvtYAKYdFiDJP68RGcUxS3YYeMD7ExgpZzMMgzvKLoce4SJx5YBAQBLSPTf1kzT5aH4qqj6BbjyurzqRbjyG6wXJfBdwo", + }, + }, + ] + /listBlockHeaders: + get: + summary: Get block headers list + tags: [Block] + parameters: + - name: height + in: query + require: true + schema: + type: integer + example: 1 + - name: size + in: query + require: true + schema: + type: integer + example: 5 + - name: offset + in: query + require: true + schema: + type: integer + example: 0 + - name: asc + in: query + require: true + schema: + type: bool + example: true + responses: + "200": + description: Successful response + content: + application/json: + example: + blocks: + - hash: "lK4dq/zpT/Y8cSSdebJY9j8FmGJHjKG4SJF4r4++a8E=" + header: + chainID: "AwAAAAAAYm10LmFlcmdvLmlvL3JhZnQ=" + timestamp: 1559883600000000000 + blocksRootHash: "4YQSYp0dkqUHYzTLAciMW9ZDc3RU89VMYuFBpfyHxaQ=" + txsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + receiptsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + /getBlockMetadata: + get: + summary: Get block meta + tags: [Block] + parameters: + - name: hash + in: query + schema: + type: string + - name: number + in: query + schema: + type: integer + example: 1 + responses: + "200": + description: Successful response + content: + application/json: + example: + hash: "A7L0SFNlkIAGivGOMKyzQIeaIbIZzUN0KiB/XZzzyRU=" + header: + chainID: "AwAAAAAAYm10LmFlcmdvLmlvL3JhZnQ=" + prevBlockHash: "lK4dq/zpT/Y8cSSdebJY9j8FmGJHjKG4SJF4r4++a8E=" + blockNo: 1 + timestamp: 1692161210463122000 + blocksRootHash: "4YQSYp0dkqUHYzTLAciMW9ZDc3RU89VMYuFBpfyHxaQ=" + txsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + receiptsRootHash: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + pubKey: "CAISIQNmmKv+inhXk+dWY2XdX8MxkZk7SCxlBoXSrAaYKsweFQ==" + sign: "MEUCIQC1ZOHY2Yi1tMl6zi4uvM5Rd2Mo+JAmlzYBNFd9hC7aRQIgbR+b5bhz06OBElZaiMt60gQK6KakgQjDZvomSRPIWEM=" + size: 324 + /getTx: + get: + summary: Get transaction information + tags: [Transaction] + parameters: + - name: hash + in: query + schema: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "txIdx": + { + "blockHash": "Dss9zEetcSJy8tr7uLxUr9C8LLjLj5XQYZsfkm7jp1sa", + "idx": 0, + }, + "tx": + { + "hash": "8EiUrFKE2ivrmbP21iAusiuHUViVdJPwqLgX2byZuRN1", + "body": + { + "nonce": 1, + "account": "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj", + "recipient": "AmQ2tS46doLfqsH4vy7PXmvGn7paec8wgqPWmGrcJKiLZADrJcME", + "amount": "1000000000000000000", + "type": 4, + "chainIdHash": "BNVSYKKqSR78hTSrxkaroFK2S3mgsoocpWg9HYtb1Zhn", + "sign": "AN1rKvtRfKap2yDrBAp3gefs2YyS362oTCWPQk1oEYVwsyRm4MfMDtvbAPazr5NPBBP9TJHda8yzCXQXz2hnzrc5WRoaWBxqd", + }, + }, + } + /getReceipt: + get: + summary: Get a receipt + tags: [Transaction] + parameters: + - name: hash + in: query + schema: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "contractAddress": "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + "status": "SUCCESS", + "ret": "", + "txHash": "C5r1VqkYWnBAHtEqJBHUjXPzbKG9f7QYXNEd5sD2KdQb", + "feeUsed": "0", + "cumulativeFeeUsed": "0", + "blockHash": "HRdfbN8Pp9iUuknJ7SWaRbKdy23x5zruLL7dBdVLFdxS", + "blockNo": 8, + "from": "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R", + "to": "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + } + /getReceipts: + get: + summary: Get receipt list in block + tags: [Transaction] + parameters: + - name: hash + in: query + schema: + type: string + - name: number + in: query + schema: + type: integer + - name: offset + in: query + schema: + type: integer + example: 0 + - name: size + in: query + schema: + type: integer + example: 100 + responses: + "200": + description: Successful response + content: + application/json: + example: + { + total: 2, + size: 100, + receipts: + [ + { + contractAddress: "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + status: "SUCCESS", + ret: "", + txHash: "6kj45qbgaaKMtQLfPmvTPdU3ozipMX46P5ApSa8Jw1rn", + feeUsed: "0", + cumulativeFeeUsed: "0", + blockHash: "A6Y8J1J9w5ZB8jzBemKZNyrbRZpNDeRsdFnukUqPEQak", + blockNo: 39, + from: "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R", + to: "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + }, + { + contractAddress: "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + status: "SUCCESS", + ret: "", + txHash: "8bmgNcZP7YeTzxWmkn9kJD5hvD4z2BVGq1tQJ5Vj76Ur", + feeUsed: "0", + cumulativeFeeUsed: "0", + blockHash: "A6Y8J1J9w5ZB8jzBemKZNyrbRZpNDeRsdFnukUqPEQak", + blockNo: 39, + txIndex: 1, + from: "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R", + to: "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + }, + ], + blockNo: 39, + } + /sendSignedTransaction: + post: + summary: Commit transaction to aergo server + tags: [Transaction] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + hash: + type: string + body: + type: object + properties: + nonce: + type: integer + account: + type: string + recipient: + type: string + amount: + type: string + payloadJson: + type: object + properties: + name: + type: string + args: + type: array + items: + type: string + chainIdHash: + type: string + sign: + type: string + type: + type: integer + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "results": + [ + { + "hash": "pK5LKSZ1mKIJyGJSkM4T496pCTu1xQhXv8ZBdjXTkkg=", + }, + ], + } + /queryContract: + get: + summary: Call to execute a smart contract + tags: [Transaction] + parameters: + - name: address + in: query + schema: + type: string + example: AmgRLghGjbbWsPm4CZqaJEtxRdv3f5navmhVwn8phpXa7FyQBH6e + - name: name + in: query + schema: + type: string + example: get_value + - name: query + in: query + schema: + type: string + example: '["name"]' + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "Hash": "GwnLivMvAXKqoRCwQkhvrm8biC4JhpcJgmBL84iBnPtp", + "Body": + { + "Nonce": 2, + "Account": "AmPpcKvToDCUkhT1FJjdbNvR4kNDhLFJGHkSqfjWe3QmHm96qv4R", + "Recipient": "AmM1M4jAxeTxfh9CHUmKXJwTLxGJ6Qe5urEeFXqbqkKnZ73Uvx4y", + "Amount": "1000000000000000000", + "Type": 4, + "ChainIdHash": "BNVSYKKqSR78hTSrxkaroFK2S3mgsoocpWg9HYtb1Zhn", + "Sign": "AN1rKvtDAYfLhSXNHCXLzi36a7ZjNbp7jhsJoM5ZdyLNZbGLgqDaDRPoNYFwUCVwpCVMqf3kLotB1jmiYMBzjmGeWN7pmsSDs", + }, + } + /listEvents: + get: + summary: List event + tags: [Transaction] + parameters: + - name: address + in: query + schema: + type: string + - name: eventName + in: query + schema: + type: string + - name: blockfrom + in: query + schema: + type: integer + example: 0 + - name: blockto + in: query + schema: + type: integer + example: 0 + - name: desc + in: query + schema: + type: boolean + example: false + - name: argFilter + in: query + schema: + type: string + - name: recentBlockCnt + in: query + schema: + type: integer + example: 0 + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "events": + [ + { + "contractAddress": "AmggBF4etmnuX2rHAtKszY2GJyLXrhN3Md7tL2Y3s8JKd99xLsak", + "eventName": "save", + "Args": ["name", "hong"], + "txHash": "6bJ7RBRAE6wFPRRQVH2TUGzQt93mHEZjSVbsPYnGxpve", + "EventIdx": 0, + "BlockHash": "AahyLGkriDwqCGR4xBfgxUPo95r4ySqbiVDXUq5HJETS", + "BlockNo": 9981, + "TxIndex": 0, + }, + ], + } + /getABI: + get: + summary: Get ABI of the contract + tags: [Transaction] + parameters: + - name: address + in: query + schema: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "version": "0.2", + "language": "lua", + "functions": + [ + { + "name": "get_value", + "arguments": [{ "name": "key" }], + "view": true, + }, + { + "name": "set_value", + "arguments": [{ "name": "key" }, { "name": "value" }], + }, + ], + "state_variables": [{ "name": "values", "type": "map" }], + } + /queryContractStateProof: + get: + summary: Query the state of a contract with variable name and optional index + tags: [Transaction] + parameters: + - name: address + in: query + schema: + type: string + - name: varname1 + in: query + schema: + type: string + - name: varname2 + in: query + schema: + type: string + - name: compressed + in: query + schema: + type: boolean + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "contractProof": + { + "key": "Qgw+1I9p/nXaN8M1izpuXapIXs04nV1JR0+NtnXiLIazyCPt8Ac=", + "proofKey": "B9a8HHz3U62tm8+6SrVPp7Ic7fFSJxDnxPxpT9UjkO0=", + "proofVal": "67rvGuYueuLnut1L40fXb/4DhgxGQBy74GJGrReJerg=", + "auditPath": + ["bTxu9o0zPDGZdyb3Lyvd2R5FSmDVtnIKA92I6IrL+Ws="], + }, + } + /getTxCount: + get: + summary: Transaction number in block + tags: [Transaction] + parameters: + - name: hash + in: query + schema: + type: string + - name: number + in: query + schema: + type: integer + responses: + "200": + description: Successful response + content: + application/json: + example: + count: 10 + /getChainInfo: + get: + summary: Get current blockchain information + tags: [Etc] + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "id": + { + "magic": "bmt.aergo.io", + "consensus": "raft", + "version": 3, + }, + "bpNumber": 3, + "maxblocksize": 1000400, + "maxtokens": "700000000000000000000000000", + "gasprice": "50000000000", + "nameprice": "1000000000000000000", + "totalvotingpower": "0", + "votingreward": "0", + } + /getConsensusInfo: + get: + summary: Get consensus information + tags: [Etc] + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "type": "raft", + "info": + { + "Leader": "id=0", + "Name": "local1", + "RaftId": "6d3862958785a554", + "Status": + { + "applied": 3, + "commit": 3, + "id": "6d3862958785a554", + "lead": "0", + "leadtransferee": "0", + "progress": {}, + "raftState": "StateCandidate", + "term": 6, + "vote": "6d3862958785a554", + }, + "Total": 3, + }, + "bps": + [ + { + "Addr": "/ip4/192.168.0.21/tcp/7846", + "Name": "local1", + "PeerID": "16Uiu2HAmKZUxcDNLo6BbhugZ8XfgdC4utmJ5M6HTjvFmYZ2tqTac", + "RaftID": "6d3862958785a554", + }, + { + "Addr": "/ip4/192.168.0.21/tcp/27846", + "Name": "local2", + "PeerID": "16Uiu2HAm82vRXcL187jRhFW9jnNjueMc7bSJLdcydNWUwxeUCC2U", + "RaftID": "57779d37986cc00", + }, + { + "Addr": "/ip4/192.168.0.21/tcp/37846", + "Name": "local3", + "PeerID": "16Uiu2HAkyDXhsCj63R3ufsrYaK72nFT1RLVg5upabi6WAQtJzGoM", + "RaftID": "cf615af53daf2c6b", + }, + ], + } + /getAccountVotes: + get: + summary: Show voting stat + tags: [Etc] + parameters: + - name: account + in: query + # example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + schema: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + { + voting: + [ + { id: "voteBP" }, + { id: "BPCOUNT" }, + { id: "STAKINGMIN" }, + { id: "GASPRICE" }, + { id: "NAMEPRICE" }, + ], + } + /getNodeInfo: + get: + summary: Show internal metric + tags: [Etc] + parameters: + - name: timeout + in: query + schema: + type: integer + example: 3 + - name: component + in: query + schema: + type: string + example: "" + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "AccountsSvc": + { + "status": "started", + "acc_processed_msg": 1, + "msg_queue_len": 0, + "msg_latency": "46.1µs", + "error": "", + "actor": + { + "config": { "UnlockTimeout": 60 }, + "personal": true, + "totalaccounts": 0, + }, + }, + "ChainSvc": + { + "status": "started", + "acc_processed_msg": 34, + "msg_queue_len": 0, + "msg_latency": "60.242µs", + "error": "", + "actor": + { + "config": + { + "MaxBlockSize": 1000000, + "CoinbaseAccount": "", + "MaxAnchorCount": 20, + "VerifierCount": 8, + "ForceResetHeight": 0, + "ZeroFee": true, + "VerifyOnly": false, + "StateTrace": 0, + "VerifyBlock": 0, + "NumWorkers": 16, + "NumLStateClosers": 2, + "CloseLimit": 100, + }, + "orphan": 0, + "testmode": false, + "testnet": false, + }, + }, + "MemPoolSvc": + { + "status": "started", + "acc_processed_msg": 16, + "msg_queue_len": 0, + "msg_latency": "36.045µs", + "error": "", + "actor": + { + "config": + { + "ShowMetrics": false, + "EnableFadeout": false, + "FadeoutPeriod": 12, + "VerifierNumber": 32, + "DumpFilePath": "./data/mempool.dump", + }, + "dead": 0, + "orphan": 0, + "total": 0, + "whitelist": [], + "whitelist_on": false, + }, + }, + "RPCSvc": + { + "status": "started", + "acc_processed_msg": 14, + "msg_queue_len": 0, + "msg_latency": "27.18µs", + "error": "", + "actor": + { + "config": + { + "NetServiceAddr": "0.0.0.0", + "NetServicePort": 7845, + "NetServicePath": "", + "NetServiceTrace": false, + "NSEnableTLS": false, + "NSCert": "", + "NSKey": "", + "NSCACert": "", + "NSAllowCORS": false, + }, + "streams": { "block": 0, "blockMeta": 0, "event": 0 }, + "version": "v2.4.4-134-gaa1d7d30", + }, + }, + "SyncerSvc": + { + "status": "started", + "acc_processed_msg": 1, + "msg_queue_len": 0, + "msg_latency": "34.66µs", + "error": "", + "actor": + { + "block_added": 0, + "block_fetched": 0, + "end": 0, + "running": false, + "start": 0, + "total": 0, + }, + }, + "mapSvc": + { + "status": "started", + "acc_processed_msg": 1, + "msg_queue_len": 0, + "msg_latency": "58.423µs", + "error": "", + "actor": null, + }, + "p2pSvc": + { + "status": "started", + "acc_processed_msg": 18, + "msg_queue_len": 0, + "msg_latency": "66.815µs", + "error": "", + "actor": + { + "config": + { + "NetProtocolAddr": "192.168.0.21", + "NetProtocolPort": 7846, + "NPBindAddr": "0.0.0.0", + "NPBindPort": 7846, + "NPEnableTLS": false, + "NPCert": "", + "NPKey": "bmt1.key", + "NPAddPeers": null, + "NPHiddenPeers": null, + "NPDiscoverPeers": false, + "NPMaxPeers": 100, + "NPPeerPool": 100, + "NPExposeSelf": false, + "NPUsePolaris": false, + "NPAddPolarises": null, + "LogFullPeerID": false, + "PeerRole": "producer", + "Producers": null, + "InternalZones": null, + "Agent": "", + }, + "netstat": + { + "in": 35885, + "out": 26500, + "since": "2023-08-25T17:58:05.12366+09:00", + }, + "status": + { + "ID": "16Uiu2HAmKZUxcDNLo6BbhugZ8XfgdC4utmJ5M6HTjvFmYZ2tqTac", + "Role": "Producer", + "ProducerIDs": + [ + "16Uiu2HAmKZUxcDNLo6BbhugZ8XfgdC4utmJ5M6HTjvFmYZ2tqTac", + ], + "Addresses": ["/ip4/192.168.0.21/tcp/7846"], + "Version": "v2.4.4-134-gaa1d7d30", + "Hidden": true, + }, + "syncman": + { + "blocks": 0, + "transactions": + { + "backCache": 0, + "frontCache": 0, + "queryQueue": 0, + }, + }, + "whitelist": [], + "whitelist_on": false, + }, + }, + } + /getChainId: + get: + summary: Get ChainID + tags: [Etc] + responses: + "200": + description: Successful response + content: + application/json: + example: { magic: "bmt.aergo.io", consensus: "raft", version: 3 } + /getPeers: + get: + summary: Get Peer list + tags: [Etc] + parameters: + - name: noHidden + in: query + schema: + type: boolean + example: false + - name: showSelf + in: query + schema: + type: boolean + example: false + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "peers": + [ + { + "address": + { + "address": "192.168.0.21", + "port": 37846, + "peerID": "ACUIAhIhAjhSa/+nVg43tNhkJnaVx+xPjarZhVzd2PITk7uuJ8C0", + "role": "Producer", + "version": "v2.4.1", + "addresses": ["/ip4/192.168.0.21/tcp/37846"], + "producerIDs": + [ + "ACUIAhIhAjhSa/+nVg43tNhkJnaVx+xPjarZhVzd2PITk7uuJ8C0", + ], + }, + "bestblock": + { + "blockHash": "2Sw66KMI9ACsHxdekMnzgO422nA+3M7wlq7LGAuxDD0=", + "blockNo": 166, + }, + "state": 2, + "hidden": true, + "lashCheck": 1692954055949754000, + "version": "v2.4.1", + "acceptedRole": "Producer", + }, + { + "address": + { + "address": "192.168.0.21", + "port": 27846, + "peerID": "ACUIAhIhArtT5yoZccZO1OKI5stQRZf+h6dMC+KSg2UvdS1YY0Ep", + "role": "Producer", + "version": "v2.4.1", + "addresses": ["/ip4/192.168.0.21/tcp/27846"], + "producerIDs": + [ + "ACUIAhIhArtT5yoZccZO1OKI5stQRZf+h6dMC+KSg2UvdS1YY0Ep", + ], + }, + "bestblock": + { + "blockHash": "zr34lRpn8ZKLMaM5wBP0NVM5B8Bm+/bZo4AXUtosvWA=", + "blockNo": 118, + }, + "state": 2, + "hidden": true, + "lashCheck": 1692954008028108000, + "version": "v2.4.1", + "acceptedRole": "Producer", + }, + ], + } + /getServerInfo: + get: + summary: Show configs and status of server + tags: [Etc] + parameters: + - name: key + in: query + schema: + type: array + items: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "status": + { + "addr": "192.168.0.21", + "id": "16Uiu2HAmKZUxcDNLo6BbhugZ8XfgdC4utmJ5M6HTjvFmYZ2tqTac", + "port": "7846", + "version": "v2.4.4-134-gaa1d7d30", + }, + "config": + { + "account": { "props": { "unlocktimeout": "60" } }, + "base": { "props": { "personal": "true" } }, + }, + } + /getStaking: + get: + summary: Get the staking info from the address + tags: [Etc] + parameters: + - name: address + in: query + schema: + type: string + example: "AmNdzAYv3dYKFtPRgfUMGppGwBJS2JvZLRTF9gRruF49vppEepgj" + responses: + "200": + description: Successful response + content: + application/json: + example: { amount: "10000000000000000000000", when: 255439 } + /getVotes: + get: + summary: Show voting stat + tags: [Etc] + parameters: + - name: id + in: query + schema: + type: string + - name: count + in: query + schema: + type: integer + responses: + "200": + description: Successful response + content: + application/json: + example: + { + votes: + [ + { + candidate: "16Uiu2HAkyDXhsCj63R3ufsrYaK72nFT1RLVg5upabi6WAQtJzGoM", + amount: "162179999999999990562816", + }, + { + candidate: "16Uiu2HAmKZUxcDNLo6BbhugZ8XfgdC4utmJ5M6HTjvFmYZ2tqTac", + amount: "162179999999999990562816", + }, + { + candidate: "16Uiu2HAm82vRXcL187jRhFW9jnNjueMc7bSJLdcydNWUwxeUCC2U", + amount: "162179999999999990562816", + }, + ], + } + /metric: + get: + summary: Show metric informations + tags: [Etc] + parameters: + - name: type + in: query + schema: + type: string + enum: ["NOTHING", "P2P_NETWORK"] + example: "P2P_NETWORK" + responses: + "200": + description: Successful response + content: + application/json: + example: + { + "peers": + [ + { + "peerID": "ACUIAhIhArtT5yoZccZO1OKI5stQRZf+h6dMC+KSg2UvdS1YY0Ep", + "sumIn": 163708, + "avrIn": 19497, + "sumOut": 220664, + "avrOut": 26261, + }, + { + "peerID": "ACUIAhIhAjhSa/+nVg43tNhkJnaVx+xPjarZhVzd2PITk7uuJ8C0", + "sumIn": 165021, + "avrIn": 19651, + "sumOut": 221601, + "avrOut": 26367, + }, + ], + } + /chainStat: + get: + summary: Get chain statistics + tags: [Etc] + responses: + "200": + description: Successful response + content: + application/json: + example: { "report": { "ReorgStat": { "Count": 0 } } } + /getEnterpriseConfig: + get: + summary: Get config values of enterprise + tags: [Etc] + parameters: + - name: key + in: query + schema: + type: string + responses: + "200": + description: Successful response + content: + application/json: + example: + Key: "" + On: false + Values: "" diff --git a/rpc/web3/v1.go b/rpc/web3/v1.go new file mode 100644 index 000000000..46db2c9ec --- /dev/null +++ b/rpc/web3/v1.go @@ -0,0 +1,1270 @@ +package web3 + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "encoding/json" + "errors" + "io" + "math/big" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/aergoio/aergo/v2/contract/system" + "github.com/aergoio/aergo/v2/internal/common" + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/rpc" + "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" +) + +type APIHandler func() (http.Handler, bool) + +type Web3APIv1 struct { + rpc *rpc.AergoRPCService + request *http.Request + + handlerMap map[string]map[string]APIHandler +} + +func (api *Web3APIv1) NewHandler() { + handlerGet := map[string]APIHandler{ + "/getState": api.GetState, + "/getStateAndProof": api.GetStateAndProof, + "/getBalance": api.GetBalance, + "/getBlock": api.GetBlock, + "/blockchain": api.Blockchain, + "/getBlockTx": api.GetBlockBody, + "/listBlockHeaders": api.ListBlockHeaders, + "/getBlockMetadata": api.GetBlockMetadata, + "/getTx": api.GetTX, + "/getReceipt": api.GetReceipt, + "/getReceipts": api.GetReceipts, + "/queryContract": api.QueryContract, + "/listEvents": api.ListEvents, + "/getABI": api.GetABI, + "/queryContractStateProof": api.QueryContractState, + "/getTxCount": api.GetBlockTransactionCount, + "/getChainInfo": api.GetChainInfo, + "/getConsensusInfo": api.GetConsensusInfo, + "/getNodeInfo": api.NodeState, + "/getChainId": api.GetChainId, + "/getPeers": api.GetPeers, + "/getServerInfo": api.GetServerInfo, + "/metric": api.Metric, + "/chainStat": api.ChainStat, + } + + ca := api.rpc.GetActorHelper().GetChainAccessor() + consensus := &jsonrpc.InOutConsensusInfo{} + json.Unmarshal([]byte(ca.GetConsensusInfo()), consensus) + + if consensus.Type == "raft" { + handlerGet["/getEnterpriseConfig"] = api.GetEnterpriseConfig + } else if consensus.Type == "dpos" { + handlerGet["/getStaking"] = api.GetStaking + handlerGet["/getVotes"] = api.GetVotes + handlerGet["/getAccountVotes"] = api.GetAccountVotes + handlerGet["/getNameInfo"] = api.GetNameInfo + } + + handlerPost := map[string]APIHandler{ + "/sendSignedTransaction": api.CommitTX, + } + + api.handlerMap = make(map[string]map[string]APIHandler) + api.handlerMap[http.MethodGet] = handlerGet + api.handlerMap[http.MethodPost] = handlerPost +} + +func (api *Web3APIv1) handler(w http.ResponseWriter, r *http.Request) { + defer func() { + if rc := recover(); rc != nil { + logger.Error().Msg("panic web3 : " + r.URL.Path + "?" + r.URL.RawQuery) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }() + + api.request = r + handler, ok := api.restAPIHandler(r) + if ok { + handler.ServeHTTP(w, r) + } else { + http.NotFound(w, r) + } +} + +func (api *Web3APIv1) restAPIHandler(r *http.Request) (handler http.Handler, ok bool) { + path := r.URL.Path + path = strings.TrimPrefix(path, prefixV1) + + selectedHandler := api.handlerMap[r.Method][path] + + if selectedHandler != nil { + + return selectedHandler() + } + + return nil, false +} + +func (api *Web3APIv1) GetState() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.SingleBytes{} + account := values.Get("account") + if account != "" { + accountBytes, err := types.DecodeAddress(account) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = accountBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: account"), http.StatusBadRequest), true + } + + result, err := api.rpc.GetState(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + output := jsonrpc.ConvState(result) + output.Account = account + + // Staking, NextAction + resultStaking, err := api.rpc.GetStaking(api.request.Context(), &types.AccountAddress{Value: request.Value}) + if err == nil { + output.Stake = new(big.Int).SetBytes(resultStaking.GetAmount()).String() + output.When = resultStaking.GetWhen() + if output.When != 0 { + output.NextAction = output.When + 86400 + } + } + + var balanceInt, stakeInt *big.Int + if output.Balance != "" { + balanceInt, _ = new(big.Int).SetString(output.Balance, 10) + } else { + balanceInt = new(big.Int) + } + + if output.Stake != "" { + stakeInt, _ = new(big.Int).SetString(output.Stake, 10) + } else { + stakeInt = new(big.Int) + } + + totalInt := new(big.Int).Add(stakeInt, balanceInt) + output.Total = totalInt.String() + + // VotingPower + resultVotingPower, err := api.rpc.GetAccountVotes(api.request.Context(), &types.AccountAddress{Value: request.Value}) + if err == nil && resultVotingPower.Voting != nil { + sum := new(big.Int) + for _, vote := range resultVotingPower.Voting { + m, ok := new(big.Int).SetString(vote.GetAmount(), 10) + if ok { + sum.Add(sum, m) + } + } + output.VotingPower = sum.String() + } + + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetStateAndProof() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.AccountAndRoot{} + account := values.Get("account") + if account != "" { + accountBytes, err := types.DecodeAddress(account) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Account = accountBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: account"), http.StatusBadRequest), true + } + + compressed := values.Get("compressed") + if compressed != "" { + compressedValue, parseErr := strconv.ParseBool(compressed) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Compressed = compressedValue + } + + result, err := api.rpc.GetStateAndProof(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvStateAndPoof(result) + output.Account = account + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetNameInfo() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.Name{} + name := values.Get("name") + if name != "" { + request.Name = name + + if len(name) > types.NameLength { + return commonResponseHandler(&types.Empty{}, errors.New("name is not in format 12 digits")), true + } + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: name"), http.StatusBadRequest), true + } + + number := values.Get("number") + if number != "" { + numberValue, parseErr := strconv.ParseUint(number, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.BlockNo = numberValue + } + + result, err := api.rpc.GetNameInfo(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvNameInfo(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetBalance() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.SingleBytes{} + account := values.Get("account") + if account != "" { + accountBytes, err := types.DecodeAddress(account) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = accountBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: account"), http.StatusBadRequest), true + } + + result, err := api.rpc.GetState(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvBalance(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetBlock() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.SingleBytes{} + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } + + number := values.Get("number") + if number != "" { + numberValue, err := strconv.ParseUint(number, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + number := uint64(numberValue) // Replace with your actual value + byteValue := make([]byte, 8) + binary.LittleEndian.PutUint64(byteValue, number) + request.Value = byteValue + } + + result, err := api.rpc.GetBlock(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvBlock(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetBlockTransactionCount() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.SingleBytes{} + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } + + number := values.Get("number") + if number != "" { + numberValue, err := strconv.ParseUint(number, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + number := uint64(numberValue) // Replace with your actual value + byteValue := make([]byte, 8) + binary.LittleEndian.PutUint64(byteValue, number) + request.Value = byteValue + } + + result, err := api.rpc.GetBlock(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvBlockTransactionCount(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) Blockchain() (handler http.Handler, ok bool) { + ca := api.rpc.GetActorHelper().GetChainAccessor() + last, err := ca.GetBestBlock() + if err != nil { + return nil, false + } + + digest := sha256.New() + digest.Write(last.GetHeader().GetChainID()) + bestChainIDHash := digest.Sum(nil) + + chainInfo, err := api.rpc.GetChainInfo(api.request.Context(), &types.Empty{}) + if err != nil { + logger.Warn().Err(err).Msg("failed to get chain info in blockchain") + chainInfo = nil + } + + result := &types.BlockchainStatus{ + BestBlockHash: last.BlockHash(), + BestHeight: last.GetHeader().GetBlockNo(), + ConsensusInfo: ca.GetConsensusInfo(), + BestChainIdHash: bestChainIDHash, + ChainInfo: chainInfo, + } + + output := jsonrpc.ConvBlockchainStatus(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetBlockBody() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.BlockBodyParams{} + request.Paging = &types.PageParams{} + + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Hashornumber = hashBytes + } + + number := values.Get("number") + if number != "" { + numberValue, err := strconv.ParseUint(number, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + number := uint64(numberValue) + byteValue := make([]byte, 8) + binary.LittleEndian.PutUint64(byteValue, number) + request.Hashornumber = byteValue + } + + size := values.Get("size") + if size != "" { + sizeValue, parseErr := strconv.ParseUint(size, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + + } + request.Paging.Size = uint32(sizeValue) + if request.Paging.Size > 100 { + request.Paging.Size = 100 + } + } + + offset := values.Get("offset") + if offset != "" { + offsetValue, parseErr := strconv.ParseUint(offset, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Paging.Offset = uint32(offsetValue) + } + + result, err := api.rpc.GetBlockBody(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvBlockBodyPaged(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) ListBlockHeaders() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.ListParams{} + height := values.Get("height") + if height != "" { + heightValue, parseErr := strconv.ParseUint(height, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Height = heightValue + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: height"), http.StatusBadRequest), true + } + + size := values.Get("size") + if size != "" { + sizeValue, parseErr := strconv.ParseUint(size, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Size = uint32(sizeValue) + if request.Size > 100 { + request.Size = 100 + } + } else { + request.Size = 1 + } + + offset := values.Get("offset") + if offset != "" { + offsetValue, parseErr := strconv.ParseUint(offset, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Offset = uint32(offsetValue) + } + + asc := values.Get("asc") + if asc != "" { + ascValue, parseErr := strconv.ParseBool(asc) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Asc = ascValue + } + + result, err := api.rpc.ListBlockHeaders(api.request.Context(), request) + if err != nil { + errStr := err.Error() + + strBlockNo := strings.TrimPrefix(errStr, "block not found: blockNo=") + if strBlockNo == errStr { + return commonResponseHandler(&types.Empty{}, err), true + } + return commonResponseHandler(&types.Empty{}, errors.New("rpc error: code = Internal desc = "+errStr)), true + } + + output := jsonrpc.ConvBlockHeaderList(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetBlockMetadata() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.SingleBytes{} + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } + + number := values.Get("number") + if number != "" { + numberValue, err := strconv.ParseUint(number, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + number := uint64(numberValue) // Replace with your actual value + byteValue := make([]byte, 8) + binary.LittleEndian.PutUint64(byteValue, number) + request.Value = byteValue + } + + result, err := api.rpc.GetBlockMetadata(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvBlockMetadata(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) ListBlockMetadata() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.ListParams{} + height := values.Get("height") + if height != "" { + heightValue, parseErr := strconv.ParseUint(height, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Height = heightValue + } + + size := values.Get("size") + if size != "" { + sizeValue, parseErr := strconv.ParseUint(size, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Size = uint32(sizeValue) + if request.Size > 100 { + request.Size = 100 + } + } + + offset := values.Get("offset") + if offset != "" { + offsetValue, parseErr := strconv.ParseUint(offset, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Offset = uint32(offsetValue) + } + + asc := values.Get("asc") + if asc != "" { + ascValue, parseErr := strconv.ParseBool(asc) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Asc = ascValue + } + + result, err := api.rpc.ListBlockMetadata(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvListBlockMetadata(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetChainInfo() (handler http.Handler, ok bool) { + request := &types.Empty{} + + result, err := api.rpc.GetChainInfo(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvChainInfo(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) ChainStat() (handler http.Handler, ok bool) { + request := &types.Empty{} + + result, err := api.rpc.ChainStat(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvChainStat(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetConsensusInfo() (handler http.Handler, ok bool) { + request := &types.Empty{} + + result, err := api.rpc.GetConsensusInfo(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvConsensusInfo(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetReceipt() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.SingleBytes{} + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: hash"), http.StatusBadRequest), true + } + + result, err := api.rpc.GetReceipt(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvReceipt(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetReceipts() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.ReceiptsParams{} + request.Paging = &types.PageParams{} + + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Hashornumber = hashBytes + } + + number := values.Get("number") + if number != "" { + numberValue, err := strconv.ParseUint(number, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + number := uint64(numberValue) // Replace with your actual value + byteValue := make([]byte, 8) + binary.LittleEndian.PutUint64(byteValue, number) + request.Hashornumber = byteValue + } + + size := values.Get("size") + if size != "" { + sizeValue, parseErr := strconv.ParseUint(size, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + + } + request.Paging.Size = uint32(sizeValue) + if request.Paging.Size > 100 { + request.Paging.Size = 100 + } + } + + offset := values.Get("offset") + if offset != "" { + offsetValue, parseErr := strconv.ParseUint(offset, 10, 64) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Paging.Offset = uint32(offsetValue) + } + + result, err := api.rpc.GetReceipts(api.request.Context(), request) + if err != nil { + errStr := err.Error() + + strBlockNo := strings.TrimPrefix(errStr, "empty : blockNo=") + if strBlockNo == errStr { + return commonResponseHandler(&types.Empty{}, err), true + } + + blockNo, err := strconv.ParseUint(strBlockNo, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + receipts := &jsonrpc.InOutReceipts{} + receipts.BlockNo = blockNo + return stringResponseHandler(jsonrpc.MarshalJSON(receipts), nil), true + } + + output := jsonrpc.ConvReceiptsPaged(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetTX() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.SingleBytes{} + hash := values.Get("hash") + if hash != "" { + hashBytes, err := base58.Decode(hash) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: hash"), http.StatusBadRequest), true + } + + result, err := api.rpc.GetTX(api.request.Context(), request) + if err == nil { + output := jsonrpc.ConvTx(result, jsonrpc.Base58) + jsonrpc.CovPayloadJson(output) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true + } else { + resultblock, err := api.rpc.GetBlockTX(api.request.Context(), request) + + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvTxInBlock(resultblock, jsonrpc.Base58) + jsonrpc.CovPayloadJson(output.Tx) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true + } +} + +func (api *Web3APIv1) QueryContract() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.Query{} + address := values.Get("address") + if address != "" { + hashBytes, err := types.DecodeAddress(address) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.ContractAddress = hashBytes + } + + var ci types.CallInfo + name := values.Get("name") + if name != "" { + ci.Name = name + } + + query := values.Get("query") + + if query != "" { + err = json.Unmarshal([]byte(query), &ci.Args) + + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + } + + callinfo, err := json.Marshal(ci) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request.Queryinfo = callinfo + + result, err := api.rpc.QueryContract(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvQueryContract(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) ListEvents() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.FilterInfo{} + address := values.Get("address") + if address != "" { + hashBytes, err := types.DecodeAddress(address) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.ContractAddress = hashBytes + } + + EventName := values.Get("eventName") + if EventName != "" { + request.EventName = EventName + } + + Blockfrom := values.Get("blockfrom") + if Blockfrom != "" { + BlockfromValue, err := strconv.ParseUint(Blockfrom, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Blockfrom = uint64(BlockfromValue) + } + + Blockto := values.Get("blockto") + if Blockto != "" { + BlocktoValue, err := strconv.ParseUint(Blockto, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Blockto = uint64(BlocktoValue) + } + + desc := values.Get("desc") + if desc != "" { + descValue, parseErr := strconv.ParseBool(desc) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Desc = descValue + } + + argFilter := values.Get("argFilter") + if argFilter != "" { + request.ArgFilter = []byte(argFilter) + } + + recentBlockCnt := values.Get("recentBlockCnt") + if recentBlockCnt != "" { + recentBlockCntValue, parseErr := strconv.ParseInt(recentBlockCnt, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.RecentBlockCnt = int32(recentBlockCntValue) + if request.RecentBlockCnt > 10000 { + request.RecentBlockCnt = 10000 + } + } else if Blockfrom == "" && Blockto == "" { + request.RecentBlockCnt = 10000 + } + + result, err := api.rpc.ListEvents(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + output := jsonrpc.ConvEvents(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetABI() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.SingleBytes{} + address := values.Get("address") + if address != "" { + hashBytes, err := types.DecodeAddress(address) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } + + result, err := api.rpc.GetABI(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvAbi(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetAccountVotes() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.AccountAddress{} + account := values.Get("account") + if account != "" { + hashBytes, err := types.DecodeAddress(account) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = hashBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: account"), http.StatusBadRequest), true + } + + result, err := api.rpc.GetAccountVotes(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvInOutAccountVoteInfo(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) QueryContractState() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.StateQuery{} + address := values.Get("address") + if address != "" { + addressBytes, err := types.DecodeAddress(address) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.ContractAddress = addressBytes + } else { + return commonResponseHandlerWithCode(&types.Empty{}, errors.New("Missing required parameter: address"), http.StatusBadRequest), true + } + + storageKeyPlain := bytes.NewBufferString("_sv_") + args1 := values.Get("varname1") + if args1 != "" { + storageKeyPlain.WriteString(args1) + } + args2 := values.Get("varname2") + if args2 != "" { + storageKeyPlain.WriteString("-") + storageKeyPlain.WriteString(args2) + } + + storageKey := common.Hasher([]byte(storageKeyPlain.Bytes())) + request.StorageKeys = [][]byte{storageKey} + + compressed := values.Get("compressed") + if compressed != "" { + compressedValue, parseErr := strconv.ParseBool(compressed) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.Compressed = compressedValue + } + + result, err := api.rpc.QueryContractState(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvQueryContractState(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) NodeState() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.NodeReq{} + component := values.Get("component") + if component != "" { + request.Component = []byte(component) + } + + timeout := values.Get("timeout") + byteValue := make([]byte, 8) + if timeout != "" { + timeoutValue, err := strconv.ParseUint(timeout, 10, 64) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + timeout := uint64(timeoutValue) + binary.LittleEndian.PutUint64(byteValue, timeout) + } else { + binary.LittleEndian.PutUint64(byteValue, 3) + } + request.Timeout = byteValue + + result, err := api.rpc.NodeState(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvNodeState(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetPeers() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.PeersParams{} + noHidden := values.Get("noHidden") + if noHidden != "" { + noHiddenValue, parseErr := strconv.ParseBool(noHidden) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.NoHidden = noHiddenValue + } + + showSelf := values.Get("showSelf") + if showSelf != "" { + showSelfValue, parseErr := strconv.ParseBool(showSelf) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + request.ShowSelf = showSelfValue + } + + result, err := api.rpc.GetPeers(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvPeerList(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetChainId() (handler http.Handler, ok bool) { + chainInfo, err := api.rpc.GetChainInfo(api.request.Context(), &types.Empty{}) + if err != nil { + logger.Warn().Err(err).Msg("failed to get chain info in blockchain") + chainInfo = nil + } + + output := jsonrpc.ConvChainInfo(chainInfo) + return stringResponseHandler(jsonrpc.MarshalJSON(output.Id), nil), true +} + +func (api *Web3APIv1) GetServerInfo() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.KeyParams{} + keys := values["key"] + if len(keys) > 0 { + request.Key = keys + } + + result, err := api.rpc.GetServerInfo(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvServerInfo(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetStaking() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.AccountAddress{} + address := values.Get("address") + if address != "" { + addressBytes, err := types.DecodeAddress(address) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + request.Value = addressBytes + } + + result, err := api.rpc.GetStaking(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvStaking(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) GetVotes() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + getCount := func(id string, count uint32) uint32 { + if strings.ToLower(id) == strings.ToLower(types.OpvoteBP.ID()) { + return count + } + + if count == 0 { + return 1 + } + return count + } + + request := &types.VoteParams{} + request.Id = types.OpvoteBP.ID() + + id := values.Get("id") + if id != "" { + request.Id = id + } + + reqCount := uint32(0) + count := values.Get("count") + if count != "" { + sizeValue, parseErr := strconv.ParseUint(count, 10, 32) + if parseErr != nil { + return commonResponseHandler(&types.Empty{}, parseErr), true + } + reqCount = uint32(sizeValue) + } + + var output []*jsonrpc.InOutVotes + if id == "" { + for _, i := range system.GetVotingCatalog() { + id := i.ID() + + if id != "" { + result, err := api.rpc.GetVotes(api.request.Context(), &types.VoteParams{ + Id: id, + Count: getCount(id, reqCount), + }) + + if err == nil { + subOutput := jsonrpc.ConvVotes(result, id) + subOutput.Id = id + output = append(output, subOutput) + } + } + } + } else { + request.Count = getCount(id, reqCount) + result, err := api.rpc.GetVotes(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + subOutput := jsonrpc.ConvVotes(result, id) + subOutput.Id = id + output = append(output, subOutput) + } + + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) Metric() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + // Params + request := &types.MetricsRequest{} + metricType := values.Get("type") + if metricType != "" { + request.Types = append(request.Types, types.MetricType(types.MetricType_value[metricType])) + } else { + request.Types = append(request.Types, types.MetricType(types.MetricType_value["P2P_NETWORK"])) + } + + result, err := api.rpc.Metric(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + res := jsonrpc.ConvMetrics(result) + return stringResponseHandler(jsonrpc.MarshalJSON(res), nil), true +} + +func (api *Web3APIv1) GetEnterpriseConfig() (handler http.Handler, ok bool) { + values, err := url.ParseQuery(api.request.URL.RawQuery) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + request := &types.EnterpriseConfigKey{} + + key := values.Get("key") + if key != "" { + request.Key = key + } + + result, err := api.rpc.GetEnterpriseConfig(api.request.Context(), request) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvEnterpriseConfig(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} + +func (api *Web3APIv1) CommitTX() (handler http.Handler, ok bool) { + body, err := io.ReadAll(api.request.Body) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + txs, err := jsonrpc.ParseBase58Tx([]byte(body)) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + if len(txs) > 0 && txs[0].Body.Type == types.TxType_DEPLOY { + return commonResponseHandler(&types.Empty{}, errors.New("Feature not supported yet")), true + } + + result, err := api.rpc.CommitTX(api.request.Context(), &types.TxList{Txs: txs}) + if err != nil { + return commonResponseHandler(&types.Empty{}, err), true + } + + output := jsonrpc.ConvCommitResultList(result) + return stringResponseHandler(jsonrpc.MarshalJSON(output), nil), true +} diff --git a/rpc/web3/web3.go b/rpc/web3/web3.go new file mode 100644 index 000000000..5a065e909 --- /dev/null +++ b/rpc/web3/web3.go @@ -0,0 +1,202 @@ +package web3 + +import ( + "encoding/json" + "net/http" + "os" + "reflect" + "strconv" + + "github.com/aergoio/aergo-actor/actor" + "github.com/aergoio/aergo-lib/log" + "github.com/aergoio/aergo/v2/config" + cfg "github.com/aergoio/aergo/v2/config" + "github.com/aergoio/aergo/v2/pkg/component" + "github.com/aergoio/aergo/v2/rpc" + "github.com/aergoio/aergo/v2/types/message" + + "github.com/rs/cors" + "golang.org/x/time/rate" +) + +type Status string + +const ( + CLOSE Status = "CLOSE" + OPEN Status = "OPEN" +) + +type Web3 struct { + *component.BaseComponent + cfg *cfg.Config + + web3svc *Web3APIv1 + mux *http.ServeMux + + status Status +} + +var ( + prefixV1 = "/v1" +) + +var ( + logger = log.NewLogger("web3") +) + +func NewWeb3(cfg *config.Config, rpc *rpc.AergoRPCService) *Web3 { + mux := http.NewServeMux() + + // swagger + if cfg.Web3.SwaggerPath != "" { + mux.HandleFunc("/swagger.yaml", serveSwaggerYAML(cfg.Web3.SwaggerPath)) + mux.HandleFunc("/swagger", serveSwaggerUI(cfg.Web3.SwaggerPath)) + } + + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST"}, + AllowedHeaders: []string{"*"}, + AllowCredentials: true, + }) + + // API v1 + web3svc := &Web3APIv1{rpc: rpc} + + var liminter *rate.Limiter + if cfg.Web3.MaxLimit > 0 { + liminter = rate.NewLimiter(rate.Limit(cfg.Web3.MaxLimit), 1) + } else { + liminter = rate.NewLimiter(rate.Inf, 0) + } + + limitedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !liminter.Allow() { + http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) + return + } + web3svc.handler(w, r) + }) + mux.Handle("/v1/", c.Handler(limitedHandler)) + + web3svr := &Web3{ + cfg: cfg, + web3svc: web3svc, + mux: mux, + status: CLOSE, + } + web3svr.BaseComponent = component.NewBaseComponent(message.Web3Svc, web3svr, logger) + + return web3svr +} + +func (web3 *Web3) run() { + port := getPortFromConfig(web3.cfg) + web3.status = OPEN + + err := http.ListenAndServe(":"+strconv.Itoa(port), web3.mux) + + if err != nil { + logger.Error().Msg("Web3 Server running fail: " + err.Error()) + web3.status = CLOSE + } else { + logger.Info().Msg("Web3 Server is listening on port " + strconv.Itoa(port) + "...") + } +} + +func (web3 *Web3) Statistics() *map[string]interface{} { + ret := map[string]interface{}{ + "config": web3.cfg.Web3, + "status": web3.status, + } + + return &ret +} + +func (web3 *Web3) BeforeStart() { +} + +func (web3 *Web3) AfterStart() { + logger.Info().Msg("Web3 Server Start") + web3.web3svc.NewHandler() + go web3.run() +} + +func (web3 *Web3) BeforeStop() { +} + +func (web3 *Web3) Receive(context actor.Context) { + switch msg := context.Message().(type) { + case *actor.Started, *actor.Stopping, *actor.Stopped, *component.CompStatReq: + default: + web3.Warn().Msgf("unknown msg received in web3 %s", reflect.TypeOf(msg).String()) + } +} + +func getPortFromConfig(cfg *config.Config) int { + if cfg == nil || cfg.Web3 == nil || cfg.Web3.NetServicePort == 0 { + return 80 + } + + return cfg.Web3.NetServicePort +} + +func serveSwaggerYAML(path string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + yamlContent, err := os.ReadFile(path + "swagger.yaml") + if err != nil { + http.Error(w, "Failed to read YAML file", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/x-yaml") + w.Write(yamlContent) + } +} + +func serveSwaggerUI(path string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + htmlContent, err := os.ReadFile(path + "swagger-ui.html") + if err != nil { + http.Error(w, "Failed to read HTML file", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/html") + w.Write(htmlContent) + } +} + +func commonResponseHandler(response interface{}, err error) http.Handler { + return commonResponseHandlerWithCode(response, err, http.StatusInternalServerError) +} + +func commonResponseHandlerWithCode(response interface{}, err error, code int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err != nil { + http.Error(w, err.Error(), code) + return + } + + jsonResponse, err := json.Marshal(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(jsonResponse) + }) +} + +func stringResponseHandler(response string, err error) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(response)) + }) +} diff --git a/state/README.md b/state/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/state/account.go b/state/account.go new file mode 100644 index 000000000..a2948721a --- /dev/null +++ b/state/account.go @@ -0,0 +1,200 @@ +package state + +import ( + "fmt" + "math/big" + + "github.com/aergoio/aergo/v2/state/statedb" + "github.com/aergoio/aergo/v2/types" +) + +type AccountState struct { + sdb *statedb.StateDB + id []byte + aid types.AccountID + oldState *types.State + newState *types.State + newOne bool + deploy int8 +} + +const ( + deployFlag = 0x01 << iota + redeployFlag +) + +func (as *AccountState) ID() []byte { + if len(as.id) < types.AddressLength { + return types.AddressPadding(as.id) + } + return as.IDNoPadding() +} + +func (as *AccountState) IDNoPadding() []byte { + return as.id +} + +func (as *AccountState) AccountID() types.AccountID { + return as.aid +} + +func (as *AccountState) State() *types.State { + return as.newState +} + +func (as *AccountState) SetNonce(nonce uint64) { + as.newState.Nonce = nonce +} + +func (as *AccountState) Nonce() uint64 { + return as.newState.Nonce +} + +func (as *AccountState) Balance() *big.Int { + return new(big.Int).SetBytes(as.newState.Balance) +} + +func (as *AccountState) AddBalance(amount *big.Int) { + balance := new(big.Int).SetBytes(as.newState.Balance) + as.newState.Balance = new(big.Int).Add(balance, amount).Bytes() +} + +func (as *AccountState) SubBalance(amount *big.Int) { + balance := new(big.Int).SetBytes(as.newState.Balance) + as.newState.Balance = new(big.Int).Sub(balance, amount).Bytes() +} + +func (as *AccountState) SetCodeHash(codeHash []byte) { + as.newState.CodeHash = codeHash +} + +func (as *AccountState) CodeHash() []byte { + if as.newState == nil { + return nil + } + return as.newState.CodeHash +} + +func (as *AccountState) SetRP(sqlRecoveryPoint uint64) { + as.newState.SqlRecoveryPoint = sqlRecoveryPoint +} + +func (as *AccountState) RP() uint64 { + if as.newState == nil { + return 0 + } + return as.newState.SqlRecoveryPoint +} + +func (as *AccountState) SetStorageRoot(storageRoot []byte) { + as.newState.StorageRoot = storageRoot +} + +func (as *AccountState) StorageRoot() []byte { + if as.newState == nil { + return nil + } + return as.newState.StorageRoot +} + +func (as *AccountState) IsNew() bool { + return as.newOne +} + +func (as *AccountState) IsContract() bool { + return len(as.State().CodeHash) > 0 +} + +func (as *AccountState) IsDeploy() bool { + return as.deploy&deployFlag != 0 +} + +func (as *AccountState) SetRedeploy() { + as.deploy = deployFlag | redeployFlag +} + +func (as *AccountState) IsRedeploy() bool { + return as.deploy&redeployFlag != 0 +} + +func (as *AccountState) Reset() { + as.newState = as.oldState.Clone() +} + +func (as *AccountState) PutState() error { + return as.sdb.PutState(as.aid, as.newState) +} + +//----------------------------------------------------------------------------------------------// +// global functions + +func CreateAccountState(id []byte, sdb *statedb.StateDB) (*AccountState, error) { + v, err := GetAccountState(id, sdb) + if err != nil { + return nil, err + } + if !v.newOne { + return nil, fmt.Errorf("account(%s) aleardy exists", types.EncodeAddress(v.ID())) + } + v.newState.SqlRecoveryPoint = 1 + v.deploy = deployFlag + return v, nil +} + +func GetAccountState(id []byte, states *statedb.StateDB) (*AccountState, error) { + aid := types.ToAccountID(id) + st, err := states.GetState(aid) + if err != nil { + return nil, err + } + if st == nil { + if states.Testmode { + amount := new(big.Int).Add(types.StakingMinimum, types.StakingMinimum) + return &AccountState{ + sdb: states, + id: id, + aid: aid, + oldState: &types.State{Balance: amount.Bytes()}, + newState: &types.State{Balance: amount.Bytes()}, + newOne: true, + }, nil + } + return &AccountState{ + sdb: states, + id: id, + aid: aid, + oldState: &types.State{}, + newState: &types.State{}, + newOne: true, + }, nil + } + return &AccountState{ + sdb: states, + id: id, + aid: aid, + oldState: st, + newState: st.Clone(), + }, nil +} + +func InitAccountState(id []byte, sdb *statedb.StateDB, stOld, stNew *types.State) *AccountState { + return &AccountState{ + sdb: sdb, + id: id, + aid: types.ToAccountID(id), + oldState: stOld, + newState: stNew, + } +} + +func SendBalance(sender, receiver *AccountState, amount *big.Int) error { + if sender.AccountID() == receiver.AccountID() { + return nil + } + if sender.Balance().Cmp(amount) < 0 { + return types.ErrInsufficientBalance + } + sender.SubBalance(amount) + receiver.AddBalance(amount) + return nil +} diff --git a/state/account_test.go b/state/account_test.go new file mode 100644 index 000000000..6e44a0f59 --- /dev/null +++ b/state/account_test.go @@ -0,0 +1,33 @@ +package state + +import ( + "testing" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" + "github.com/stretchr/testify/require" +) + +func TestAidWithPadding(t *testing.T) { + for _, test := range []struct { + padding bool + id []byte + expectAid string + }{ + {false, []byte(types.AergoName), "55EYudcSHptibgAWjhJGEFXzti7ggpgQTCCBgyeCa2hR"}, + {true, []byte(types.AergoName), "DkAm9mQvmRDCqAPARNhwjyrvGPgrGYmErpov8S9sbkc3"}, + {false, base58.DecodeOrNil("AmLrV7tg69KE5ZQehkjGiA7yJyDxJ25uyF96PMRptfzwozhAJbaK"), "6eULcXBWJMgjCMeGybbpfoPWVwyuidMFFjsrJJvXmF7z"}, + {true, base58.DecodeOrNil("AmLrV7tg69KE5ZQehkjGiA7yJyDxJ25uyF96PMRptfzwozhAJbaK"), "6eULcXBWJMgjCMeGybbpfoPWVwyuidMFFjsrJJvXmF7z"}, + } { + as := &AccountState{ + id: test.id, + } + var aid types.AccountID + if test.padding { + aid = types.ToAccountID(as.ID()) + } else { + aid = types.ToAccountID(as.IDNoPadding()) + } + require.Equal(t, test.expectAid, aid.String()) + } +} diff --git a/state/blockstate.go b/state/block.go similarity index 86% rename from state/blockstate.go rename to state/block.go index f603334a1..91d28f8ad 100644 --- a/state/blockstate.go +++ b/state/block.go @@ -4,20 +4,15 @@ import ( "math/big" "github.com/aergoio/aergo/v2/consensus" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" "github.com/bluele/gcache" "github.com/willf/bloom" ) -// BlockInfo contains BlockHash and StateRoot -type BlockInfo struct { - BlockHash types.BlockID - StateRoot types.HashID -} - // BlockState contains BlockInfo and statedb for block type BlockState struct { - StateDB + *statedb.StateDB BpReward big.Int // final bp reward, increment when tx executes receipts types.Receipts CCProposal *consensus.ConfChangePropose @@ -30,14 +25,6 @@ type BlockState struct { abiCache gcache.Cache } -// GetStateRoot return bytes of bi.StateRoot -func (bi *BlockInfo) GetStateRoot() []byte { - if bi == nil { - return nil - } - return bi.StateRoot.Bytes() -} - type BlockStateOptFn func(s *BlockState) func SetPrevBlockHash(h []byte) BlockStateOptFn { @@ -53,9 +40,9 @@ func SetGasPrice(gasPrice *big.Int) BlockStateOptFn { } // NewBlockState create new blockState contains blockInfo, account states and undo states -func NewBlockState(states *StateDB, options ...BlockStateOptFn) *BlockState { +func NewBlockState(states *statedb.StateDB, options ...BlockStateOptFn) *BlockState { b := &BlockState{ - StateDB: *states, + StateDB: states, codeCache: gcache.New(100).LRU().Build(), abiCache: gcache.New(100).LRU().Build(), } @@ -66,20 +53,20 @@ func NewBlockState(states *StateDB, options ...BlockStateOptFn) *BlockState { } type BlockSnapshot struct { - state Snapshot + state statedb.Snapshot storage map[types.AccountID]int } func (bs *BlockState) Snapshot() BlockSnapshot { result := BlockSnapshot{ state: bs.StateDB.Snapshot(), - storage: bs.StateDB.cache.snapshot(), + storage: bs.StateDB.Cache.Snapshot(), } return result } func (bs *BlockState) Rollback(bSnap BlockSnapshot) error { - if err := bs.StateDB.cache.rollback(bSnap.storage); err != nil { + if err := bs.StateDB.Cache.Rollback(bSnap.storage); err != nil { return err } return bs.StateDB.Rollback(bSnap.state) diff --git a/state/chainstatedb.go b/state/chain.go similarity index 84% rename from state/chainstatedb.go rename to state/chain.go index 4dbf83c7d..72560d7cd 100644 --- a/state/chainstatedb.go +++ b/state/chain.go @@ -6,15 +6,21 @@ import ( "sync" "github.com/aergoio/aergo-lib/db" + "github.com/aergoio/aergo-lib/log" "github.com/aergoio/aergo/v2/internal/common" "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/state/statedb" "github.com/aergoio/aergo/v2/types" ) +var ( + logger = log.NewLogger(statedb.StateName) +) + // ChainStateDB manages statedb and additional informations about blocks like a state root hash type ChainStateDB struct { sync.RWMutex - states *StateDB + states *statedb.StateDB store db.DB testmode bool } @@ -44,7 +50,7 @@ func (sdb *ChainStateDB) Init(dbType string, dataDir string, bestBlock *types.Bl sdb.testmode = test // init db if sdb.store == nil { - dbPath := common.PathMkdirAll(dataDir, stateName) + dbPath := common.PathMkdirAll(dataDir, statedb.StateName) sdb.store = db.NewDB(db.ImplType(dbType), dbPath) } @@ -55,7 +61,7 @@ func (sdb *ChainStateDB) Init(dbType string, dataDir string, bestBlock *types.Bl sroot = bestBlock.GetHeader().GetBlocksRootHash() } - sdb.states = NewStateDB(sdb.store, sroot, sdb.testmode) + sdb.states = statedb.NewStateDB(sdb.store, sroot, sdb.testmode) } return nil } @@ -73,21 +79,16 @@ func (sdb *ChainStateDB) Close() error { } // GetStateDB returns statedb stores account states -func (sdb *ChainStateDB) GetStateDB() *StateDB { +func (sdb *ChainStateDB) GetStateDB() *statedb.StateDB { return sdb.states } -// GetSystemAccountState returns the state of the aergo system account. -func (sdb *ChainStateDB) GetSystemAccountState() (*ContractState, error) { - return sdb.GetStateDB().GetSystemAccountState() -} - // OpenNewStateDB returns new instance of statedb given state root hash -func (sdb *ChainStateDB) OpenNewStateDB(root []byte) *StateDB { - return NewStateDB(sdb.store, root, sdb.testmode) +func (sdb *ChainStateDB) OpenNewStateDB(root []byte) *statedb.StateDB { + return statedb.NewStateDB(sdb.store, root, sdb.testmode) } -func (sdb *ChainStateDB) SetGenesis(genesis *types.Genesis, bpInit func(*StateDB, *types.Genesis) error) error { +func (sdb *ChainStateDB) SetGenesis(genesis *types.Genesis, bpInit func(*statedb.StateDB, *types.Genesis) error) error { block := genesis.Block() stateDB := sdb.OpenNewStateDB(sdb.GetRoot()) @@ -102,7 +103,7 @@ func (sdb *ChainStateDB) SetGenesis(genesis *types.Genesis, bpInit func(*StateDB } aid := types.ToAccountID([]byte(types.AergoSystem)) - scs, err := stateDB.GetSystemAccountState() + scs, err := statedb.GetSystemAccountState(stateDB) if err != nil { return err } @@ -113,10 +114,13 @@ func (sdb *ChainStateDB) SetGenesis(genesis *types.Genesis, bpInit func(*StateDB } for address, balance := range genesis.Balance { - bytes := types.ToAddress(address) - id := types.ToAccountID(bytes) if v, ok := new(big.Int).SetString(balance, 10); ok { - if err := gbState.PutState(id, &types.State{Balance: v.Bytes()}); err != nil { + accountState, err := GetAccountState(types.ToAddress(address), gbState.StateDB) + if err != nil { + return err + } + accountState.AddBalance(v) + if err := accountState.PutState(); err != nil { return err } genesis.AddBalance(v) diff --git a/state/contract.go b/state/contract.go deleted file mode 100644 index df7511ea0..000000000 --- a/state/contract.go +++ /dev/null @@ -1,200 +0,0 @@ -package state - -import ( - "bytes" - "math/big" - - "github.com/aergoio/aergo-lib/db" - "github.com/aergoio/aergo/v2/internal/common" - "github.com/aergoio/aergo/v2/internal/enc/proto" - "github.com/aergoio/aergo/v2/types" -) - -func (states *StateDB) OpenContractStateAccount(aid types.AccountID) (*ContractState, error) { - st, err := states.GetAccountState(aid) - if err != nil { - return nil, err - } - return states.OpenContractState(aid, st) -} - -func (states *StateDB) OpenContractState(aid types.AccountID, st *types.State) (*ContractState, error) { - storage := states.cache.get(aid) - if storage == nil { - root := common.Compactz(st.StorageRoot) - storage = newBufferedStorage(root, states.store) - } - res := &ContractState{ - State: st, - account: aid, - storage: storage, - store: states.store, - } - return res, nil -} - -func (states *StateDB) StageContractState(st *ContractState) error { - states.cache.put(st.account, st.storage) - st.storage = nil - return nil -} - -// GetSystemAccountState returns the ContractState of the AERGO system account. -func (states *StateDB) GetSystemAccountState() (*ContractState, error) { - return states.OpenContractStateAccount(types.ToAccountID([]byte(types.AergoSystem))) -} - -// GetNameAccountState returns the ContractState of the AERGO name account. -func (states *StateDB) GetNameAccountState() (*ContractState, error) { - return states.OpenContractStateAccount(types.ToAccountID([]byte(types.AergoName))) -} - -// GetEnterpriseAccountState returns the ContractState of the AERGO enterprise account. -func (states *StateDB) GetEnterpriseAccountState() (*ContractState, error) { - return states.OpenContractStateAccount(types.ToAccountID([]byte(types.AergoEnterprise))) -} - -type ContractState struct { - *types.State - account types.AccountID - code []byte - storage *bufferedStorage - store db.DB -} - -func (st *ContractState) SetNonce(nonce uint64) { - st.State.Nonce = nonce -} -func (st *ContractState) GetNonce() uint64 { - return st.State.GetNonce() -} - -func (st *ContractState) SetBalance(balance *big.Int) { - st.State.Balance = balance.Bytes() -} -func (st *ContractState) GetBalance() *big.Int { - return new(big.Int).SetBytes(st.State.GetBalance()) -} - -func (st *ContractState) SetCode(code []byte) error { - codeHash := common.Hasher(code) - storedCode, err := st.GetRawKV(codeHash[:]) - if err == nil && !bytes.Equal(code, storedCode) { - err = st.SetRawKV(codeHash[:], code) - } - if err != nil { - return err - } - st.State.CodeHash = codeHash[:] - st.code = code - return nil -} - -func (st *ContractState) GetCode() ([]byte, error) { - if st.code != nil { - // already loaded. - return st.code, nil - } - codeHash := st.State.GetCodeHash() - if codeHash == nil { - // not defined. do nothing. - return nil, nil - } - err := loadData(st.store, st.State.CodeHash, &st.code) - if err != nil { - return nil, err - } - return st.code, nil -} - -func (st *ContractState) GetAccountID() types.AccountID { - return st.account -} - -// SetRawKV saves (key, value) to st.store without any kind of encoding. -func (st *ContractState) SetRawKV(key []byte, value []byte) error { - return saveData(st.store, key, value) -} - -// GetRawKV loads (key, value) from st.store. -func (st *ContractState) GetRawKV(key []byte) ([]byte, error) { - var b []byte - if err := loadData(st.store, key, &b); err != nil { - return nil, err - } - return b, nil -} - -// HasKey returns existence of the key -func (st *ContractState) HasKey(key []byte) bool { - return st.storage.has(types.GetHashID(key), true) -} - -// SetData store key and value pair to the storage. -func (st *ContractState) SetData(key, value []byte) error { - st.storage.put(newValueEntry(types.GetHashID(key), value)) - return nil -} - -// GetData returns the value corresponding to the key from the buffered storage. -func (st *ContractState) GetData(key []byte) ([]byte, error) { - id := types.GetHashID(key) - if entry := st.storage.get(id); entry != nil { - if value := entry.Value(); value != nil { - return value.([]byte), nil - } - return nil, nil - } - return st.getInitialData(id[:]) -} - -func (st *ContractState) getInitialData(id []byte) ([]byte, error) { - dkey, err := st.storage.trie.Get(id) - if err != nil { - return nil, err - } - if len(dkey) == 0 { - return nil, nil - } - value := []byte{} - if err := loadData(st.store, dkey, &value); err != nil { - return nil, err - } - return value, nil -} - -// GetInitialData returns the value corresponding to the key from the contract storage. -func (st *ContractState) GetInitialData(key []byte) ([]byte, error) { - id := types.GetHashID(key) - return st.getInitialData(id[:]) -} - -// DeleteData remove key and value pair from the storage. -func (st *ContractState) DeleteData(key []byte) error { - st.storage.put(newValueEntryDelete(types.GetHashID(key))) - return nil -} - -// Snapshot returns revision number of storage buffer -func (st *ContractState) Snapshot() Snapshot { - return Snapshot(st.storage.buffer.snapshot()) -} - -// Rollback discards changes of storage buffer to revision number -func (st *ContractState) Rollback(revision Snapshot) error { - return st.storage.buffer.rollback(int(revision)) -} - -// Hash implements types.ImplHashBytes -func (st *ContractState) Hash() []byte { - return getHashBytes(st.State) -} - -// Marshal implements types.ImplMarshal -func (st *ContractState) Marshal() ([]byte, error) { - return proto.Encode(st.State) -} - -func (st *ContractState) cache() *stateBuffer { - return st.storage.buffer -} diff --git a/state/statedb/contract.go b/state/statedb/contract.go new file mode 100644 index 000000000..21f315ae2 --- /dev/null +++ b/state/statedb/contract.go @@ -0,0 +1,196 @@ +package statedb + +import ( + "bytes" + + "github.com/aergoio/aergo-lib/db" + "github.com/aergoio/aergo/v2/internal/common" + "github.com/aergoio/aergo/v2/internal/enc/proto" + "github.com/aergoio/aergo/v2/types" +) + +type ContractState struct { + *types.State + id []byte + account types.AccountID + code []byte + storage *bufferedStorage + store db.DB +} + +func (cs *ContractState) SetCode(code []byte) error { + codeHash := common.Hasher(code) + storedCode, err := cs.GetRawKV(codeHash[:]) + if err == nil && !bytes.Equal(code, storedCode) { + err = cs.SetRawKV(codeHash[:], code) + } + if err != nil { + return err + } + cs.State.CodeHash = codeHash[:] + cs.code = code + return nil +} + +func (cs *ContractState) GetCode() ([]byte, error) { + if cs.code != nil { + // already loaded. + return cs.code, nil + } + codeHash := cs.GetCodeHash() + if codeHash == nil { + // not defined. do nothing. + return nil, nil + } + err := loadData(cs.store, cs.State.GetCodeHash(), &cs.code) + if err != nil { + return nil, err + } + return cs.code, nil +} + +func (cs *ContractState) GetAccountID() types.AccountID { + return cs.account +} + +func (cs *ContractState) GetID() []byte { + return cs.id +} + +// SetRawKV saves (key, value) to st.store without any kind of encoding. +func (cs *ContractState) SetRawKV(key []byte, value []byte) error { + return saveData(cs.store, key, value) +} + +// GetRawKV loads (key, value) from st.store. +func (cs *ContractState) GetRawKV(key []byte) ([]byte, error) { + var b []byte + if err := loadData(cs.store, key, &b); err != nil { + return nil, err + } + return b, nil +} + +// HasKey returns existence of the key +func (cs *ContractState) HasKey(key []byte) bool { + return cs.storage.has(types.GetHashID(key), true) +} + +// SetData store key and value pair to the storage. +func (cs *ContractState) SetData(key, value []byte) error { + cs.storage.put(newValueEntry(types.GetHashID(key), value)) + return nil +} + +// GetData returns the value corresponding to the key from the buffered storage. +func (cs *ContractState) GetData(key []byte) ([]byte, error) { + id := types.GetHashID(key) + if entry := cs.storage.get(id); entry != nil { + if value := entry.Value(); value != nil { + return value.([]byte), nil + } + return nil, nil + } + return cs.getInitialData(id[:]) +} + +func (cs *ContractState) getInitialData(id []byte) ([]byte, error) { + dkey, err := cs.storage.Trie.Get(id) + if err != nil { + return nil, err + } + if len(dkey) == 0 { + return nil, nil + } + value := []byte{} + if err := loadData(cs.store, dkey, &value); err != nil { + return nil, err + } + return value, nil +} + +// GetInitialData returns the value corresponding to the key from the contract storage. +func (cs *ContractState) GetInitialData(key []byte) ([]byte, error) { + id := types.GetHashID(key) + return cs.getInitialData(id[:]) +} + +// DeleteData remove key and value pair from the storage. +func (cs *ContractState) DeleteData(key []byte) error { + cs.storage.put(newValueEntryDelete(types.GetHashID(key))) + return nil +} + +// Snapshot returns revision number of storage buffer +func (cs *ContractState) Snapshot() Snapshot { + return Snapshot(cs.storage.Buffer.snapshot()) +} + +// Rollback discards changes of storage buffer to revision number +func (cs *ContractState) Rollback(revision Snapshot) error { + return cs.storage.Buffer.rollback(int(revision)) +} + +// Hash implements types.ImplHashBytes +func (cs *ContractState) Hash() []byte { + return getHashBytes(cs.State) +} + +// Marshal implements types.ImplMarshal +func (cs *ContractState) Marshal() ([]byte, error) { + return proto.Encode(cs.State) +} + +func (cs *ContractState) cache() *stateBuffer { + return cs.storage.Buffer +} + +//---------------------------------------------------------------// +// global functions + +func OpenContractStateAccount(id []byte, states *StateDB) (*ContractState, error) { + aid := types.ToAccountID(id) + st, err := states.GetAccountState(aid) + if err != nil { + return nil, err + } + return OpenContractState(id, st, states) +} + +func OpenContractState(id []byte, st *types.State, states *StateDB) (*ContractState, error) { + aid := types.ToAccountID(id) + storage := states.Cache.get(aid) + if storage == nil { + root := common.Compactz(st.StorageRoot) + storage = newBufferedStorage(root, states.Store) + } + res := &ContractState{ + State: st, + id: id, + account: aid, + storage: storage, + store: states.Store, + } + return res, nil +} + +func StageContractState(st *ContractState, states *StateDB) error { + states.Cache.put(st.account, st.storage) + st.storage = nil + return nil +} + +// GetSystemAccountState returns the ContractState of the AERGO system account. +func GetSystemAccountState(states *StateDB) (*ContractState, error) { + return OpenContractStateAccount([]byte(types.AergoSystem), states) +} + +// GetNameAccountState returns the ContractState of the AERGO name account. +func GetNameAccountState(states *StateDB) (*ContractState, error) { + return OpenContractStateAccount([]byte(types.AergoName), states) +} + +// GetEnterpriseAccountState returns the ContractState of the AERGO enterprise account. +func GetEnterpriseAccountState(states *StateDB) (*ContractState, error) { + return OpenContractStateAccount([]byte(types.AergoEnterprise), states) +} diff --git a/state/contract_test.go b/state/statedb/contract_test.go similarity index 77% rename from state/contract_test.go rename to state/statedb/contract_test.go index 8bb04a58a..6761e1608 100644 --- a/state/contract_test.go +++ b/state/statedb/contract_test.go @@ -1,30 +1,11 @@ -package state +package statedb import ( - "os" "testing" - "github.com/aergoio/aergo-lib/db" - "github.com/aergoio/aergo/v2/types" "github.com/stretchr/testify/assert" ) -var chainStateDB *ChainStateDB -var stateDB *StateDB - -func initTest(t *testing.T) { - chainStateDB = NewChainStateDB() - _ = chainStateDB.Init(string(db.BadgerImpl), "test", nil, false) - stateDB = chainStateDB.GetStateDB() - genesis := types.GetTestGenesis() - - err := chainStateDB.SetGenesis(genesis, nil) - assert.NoError(t, err, "failed init") -} -func deinitTest() { - _ = chainStateDB.Close() - _ = os.RemoveAll("test") -} func TestContractStateCode(t *testing.T) { initTest(t) defer deinitTest() @@ -32,7 +13,7 @@ func TestContractStateCode(t *testing.T) { testBytes := []byte("test_bytes") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // set code @@ -53,7 +34,7 @@ func TestContractStateData(t *testing.T) { testKey := []byte("test_key") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // set data @@ -66,7 +47,7 @@ func TestContractStateData(t *testing.T) { assert.Equal(t, testBytes, res, "different data detected") // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") } @@ -78,7 +59,7 @@ func TestContractStateInitialData(t *testing.T) { testKey := []byte("test_key") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // get initial data @@ -101,7 +82,7 @@ func TestContractStateInitialData(t *testing.T) { assert.Nil(t, res, "get initial data from contract state") // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") // update and commit statedb @@ -111,7 +92,7 @@ func TestContractStateInitialData(t *testing.T) { assert.NoError(t, err, "commit statedb") // reopen contract state - contractState, err = stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err = OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // get initial data @@ -128,15 +109,15 @@ func TestContractStateDataDelete(t *testing.T) { testKey := []byte("test_key") // open contract state and set test data - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") err = contractState.SetData(testKey, testBytes) assert.NoError(t, err, "set data to contract state") // stage and re-open contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") - contractState, err = stateDB.OpenContractState(types.ToAccountID(testAddress), contractState.State) + contractState, err = OpenContractState(testAddress, contractState.State, stateDB) assert.NoError(t, err, "could not open contract state") // get and delete test data @@ -147,9 +128,9 @@ func TestContractStateDataDelete(t *testing.T) { assert.NoError(t, err, "delete data from contract state") // stage and re-open contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") - contractState, err = stateDB.OpenContractState(types.ToAccountID(testAddress), contractState.State) + contractState, err = OpenContractState(testAddress, contractState.State, stateDB) assert.NoError(t, err, "could not open contract state") // get test data @@ -158,7 +139,7 @@ func TestContractStateDataDelete(t *testing.T) { assert.Nil(t, res, "garbage data detected") // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") } @@ -170,7 +151,7 @@ func TestContractStateHasKey(t *testing.T) { testKey := []byte("test_key") // open contract state and set test data - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") assert.False(t, contractState.HasKey(testKey)) @@ -189,7 +170,7 @@ func TestContractStateHasKey(t *testing.T) { assert.True(t, contractState.HasKey(testKey)) // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") // update and commit @@ -199,7 +180,7 @@ func TestContractStateHasKey(t *testing.T) { assert.NoError(t, err, "failed to commit stateDB") // re-open contract state - contractState, err = stateDB.OpenContractState(types.ToAccountID(testAddress), contractState.State) + contractState, err = OpenContractState(testAddress, contractState.State, stateDB) assert.NoError(t, err, "could not open contract state") // check key existence @@ -212,11 +193,11 @@ func TestContractStateEmpty(t *testing.T) { testAddress := []byte("test_address") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") } @@ -228,7 +209,7 @@ func TestContractStateReOpenData(t *testing.T) { testKey := []byte("test_key") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // set data @@ -241,12 +222,12 @@ func TestContractStateReOpenData(t *testing.T) { assert.Equal(t, testBytes, res, "different data detected") // stage contract state - err = stateDB.StageContractState(contractState) + err = StageContractState(contractState, stateDB) assert.NoError(t, err, "stage contract state") // re-open contract state - //contractState2, err := chainStateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) - contractState2, err := stateDB.OpenContractState(types.ToAccountID(testAddress), contractState.State) + //contractState2, err := chainOpenContractStateAccount(testAddress,StateDB) + contractState2, err := OpenContractState(testAddress, contractState.State, stateDB) assert.NoError(t, err, "could not open contract state") // get data @@ -263,7 +244,7 @@ func TestContractStateRollback(t *testing.T) { testKey := []byte("test_key") // open contract state - contractState, err := stateDB.OpenContractStateAccount(types.ToAccountID(testAddress)) + contractState, err := OpenContractStateAccount(testAddress, stateDB) assert.NoError(t, err, "could not open contract state") // test data diff --git a/state/statedb/dump.go b/state/statedb/dump.go new file mode 100644 index 000000000..61fe85972 --- /dev/null +++ b/state/statedb/dump.go @@ -0,0 +1,99 @@ +package statedb + +import ( + "encoding/json" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +type DumpAccount struct { + State *types.State + Code []byte + Storage map[types.AccountID][]byte +} + +func (d DumpAccount) MarshalJSON() ([]byte, error) { + mapState := make(map[string]interface{}) + mapState["nonce"] = d.State.Nonce + mapState["balance"] = d.State.GetBalanceBigInt().String() + mapState["codeHash"] = base58.Encode(d.State.CodeHash) + mapState["storageRoot"] = base58.Encode(d.State.StorageRoot) + mapState["sqlRecoveryPoint"] = d.State.SqlRecoveryPoint + + mapStorage := make(map[string]string) + for k, v := range d.Storage { + mapStorage[k.String()] = base58.Encode(v) + } + + return json.Marshal(map[string]interface{}{ + "state": mapState, + "code": string(d.Code), + "storage": mapStorage, + }) +} + +type Dump struct { + Root []byte `json:"root"` + Accounts map[types.AccountID]DumpAccount `json:"accounts"` +} + +func (d Dump) MarshalJSON() ([]byte, error) { + mapAccounts := make(map[string]DumpAccount) + for k, v := range d.Accounts { + mapAccounts[k.String()] = v + } + + return json.Marshal(map[string]interface{}{ + "root": base58.Encode(d.Root), + "accounts": mapAccounts, + }) +} + +func (sdb *StateDB) RawDump() (Dump, error) { + var err error + + self := sdb.Clone() + dump := Dump{ + Root: self.GetRoot(), + Accounts: make(map[types.AccountID]DumpAccount), + } + + for _, key := range self.Trie.GetKeys() { + var st *types.State + var code []byte + storage := make(map[types.AccountID][]byte) + + // load account state + aid := types.AccountID(types.ToHashID(key)) + st, err = sdb.getState(aid) + if err != nil { + return dump, err + } + if len(st.GetCodeHash()) > 0 { + // load code + loadData(self.Store, st.GetCodeHash(), &code) + + // load contract state + cs, err := OpenContractState(key, st, self) + if err != nil { + return dump, err + } + + // load storage + for _, key := range cs.storage.Trie.GetKeys() { + data, _ := cs.getInitialData(key) + aid := types.AccountID(types.ToHashID(key)) + storage[aid] = data + } + } + + dump.Accounts[aid] = DumpAccount{ + State: st, + Code: code, + Storage: storage, + } + } + + return dump, nil +} diff --git a/state/statedb/dump_test.go b/state/statedb/dump_test.go new file mode 100644 index 000000000..9aa208731 --- /dev/null +++ b/state/statedb/dump_test.go @@ -0,0 +1,143 @@ +package statedb + +import ( + "testing" + + "github.com/aergoio/aergo/v2/internal/common" + "github.com/aergoio/aergo/v2/types" + "github.com/stretchr/testify/require" +) + +func TestDumpAccount(t *testing.T) { + initTest(t) + defer deinitTest() + + // set account state + for _, v := range testStates { + err := stateDB.PutState(testAccount, v) + require.NoError(t, err, "failed to put state") + } + err := stateDB.Update() + require.NoError(t, err, "failed to update") + err = stateDB.Commit() + require.NoError(t, err, "failed to commit") + + dump, err := stateDB.Dump() + require.NoError(t, err) + + require.Equal(t, string(dump), `{ + "accounts": { + "9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": { + "code": "", + "state": { + "balance": "500", + "codeHash": "", + "nonce": 5, + "sqlRecoveryPoint": 0, + "storageRoot": "" + }, + "storage": {} + } + }, + "root": "G1GECbeFFqSB4WhWCFF8b6tXfc8NCpa2XMqRmgbv7gDD" +}`) +} + +func TestDumpContract(t *testing.T) { + initTest(t) + defer deinitTest() + + code := "testcode" + codeHash := common.Hasher([]byte(code)) + // save code + err := saveData(store, codeHash, []byte(code)) + require.NoError(t, err, "failed to save code") + // set contract state + err = stateDB.PutState(testAccount, &types.State{ + Balance: types.NewAmount(1, types.Aergo).Bytes(), + Nonce: 1, + CodeHash: common.Hasher([]byte(code)), + }) + require.NoError(t, err, "failed to put state") + + err = stateDB.Update() + require.NoError(t, err, "failed to update") + err = stateDB.Commit() + require.NoError(t, err, "failed to commit") + + dump, err := stateDB.Dump() + require.NoError(t, err) + + require.Equal(t, string(dump), `{ + "accounts": { + "9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": { + "code": "testcode", + "state": { + "balance": "1000000000000000000", + "codeHash": "6GBoUd26XJnkj6wGs1L6fLg8jhuVXSTVUNWzoXsjeHoh", + "nonce": 1, + "sqlRecoveryPoint": 0, + "storageRoot": "" + }, + "storage": {} + } + }, + "root": "9NfkNYKP6KKZbQ2S3CDkeWqWMfxv2zfNgETCPoqWkDmP" +}`) +} + +func TestDumpStorage(t *testing.T) { + initTest(t) + defer deinitTest() + + code := "testcode" + codeHash := common.Hasher([]byte(code)) + // set code + err := saveData(store, codeHash, []byte(code)) + require.NoError(t, err, "failed to save code") + + // set contract state + err = stateDB.PutState(testAccount, &types.State{ + Balance: types.NewAmount(1, types.Aergo).Bytes(), + Nonce: 1, + CodeHash: common.Hasher([]byte(code)), + }) + require.NoError(t, err, "failed to put state") + + // set contract storage + scs, err := OpenContractStateAccount([]byte("test_address"), stateDB) + require.NoError(t, err, "failed to open contract state account") + scs.SetData([]byte("test_storage1"), []byte("test_value1")) + scs.SetData([]byte("test_storage2"), []byte("test_value2")) + scs.SetData([]byte("test_storage3"), []byte("test_value3")) + StageContractState(scs, stateDB) + + err = stateDB.Update() + require.NoError(t, err, "failed to update") + err = stateDB.Commit() + require.NoError(t, err, "failed to commit") + + dump, err := stateDB.Dump() + require.NoError(t, err) + + require.Equal(t, string(dump), `{ + "accounts": { + "9RhQjznbYXqMQG1GmuYSsvoCYe5bnCPZCTnT6ZvohkxN": { + "code": "testcode", + "state": { + "balance": "1000000000000000000", + "codeHash": "6GBoUd26XJnkj6wGs1L6fLg8jhuVXSTVUNWzoXsjeHoh", + "nonce": 1, + "sqlRecoveryPoint": 0, + "storageRoot": "9SVveGGrFXJtoVFFiGpWZ1TmHmKLPnqoYmS7AGxzxgdL" + }, + "storage": { + "BVsyGJb6L5qLr8EPcM78Wd5NgZz3cjC1jM2FxpmZFrBm": "Vs5LyU62cV3qLve", + "ByeMAs5g3t233iEGkEzMYoN4UrnaJPJ6TGNdY2vs9MBg": "Vs5LyU62cV3qLvd", + "q6MAgzMsY2iJcukzs5x7M9WFmN4bT9AiqzviD1DcSZX": "Vs5LyU62cV3qLvc" + } + } + }, + "root": "9bQx52KdKfMkVdakKEWQLBscgkMiFwd2Zx2hr7u1GYam" +}`) +} diff --git a/state/statebuffer.go b/state/statedb/statebuffer.go similarity index 89% rename from state/statebuffer.go rename to state/statedb/statebuffer.go index b2a369e3a..bffdf4b37 100644 --- a/state/statebuffer.go +++ b/state/statedb/statebuffer.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "sort" @@ -15,10 +15,6 @@ type entry interface { Value() interface{} } -type cached interface { - cache() *stateBuffer -} - type valueEntry struct { key types.HashID value interface{} @@ -91,12 +87,11 @@ type stateBuffer struct { } func newStateBuffer() *stateBuffer { - buffer := stateBuffer{ + return &stateBuffer{ entries: []entry{}, indexes: bufferIndex{}, nextIdx: 0, } - return &buffer } func (buffer *stateBuffer) reset() error { @@ -160,7 +155,7 @@ func (buffer *stateBuffer) export() ([][]byte, [][]byte) { bufs = append(bufs, et) } sort.Slice(bufs, func(i, j int) bool { - return -1 == (bufs[i].KeyID().Compare(bufs[j].KeyID())) + return (bufs[i].KeyID().Compare(bufs[j].KeyID())) == -1 }) size := len(bufs) keys := make([][]byte, size) @@ -187,7 +182,7 @@ func (buffer *stateBuffer) updateTrie(tr *trie.Trie) error { func (buffer *stateBuffer) stage(txn trie.DbTx) error { for _, v := range buffer.indexes { et := buffer.entries[v.peek()] - buf, err := marshal(et.Value()) + buf, err := Marshal(et.Value()) if err != nil { return err } @@ -196,16 +191,16 @@ func (buffer *stateBuffer) stage(txn trie.DbTx) error { return nil } -func marshal(data interface{}) ([]byte, error) { - switch data.(type) { +func Marshal(data interface{}) ([]byte, error) { + switch msg := data.(type) { case ([]byte): - return data.([]byte), nil + return msg, nil case (*[]byte): - return *(data.(*[]byte)), nil + return *msg, nil case (types.ImplMarshal): - return data.(types.ImplMarshal).Marshal() + return msg.Marshal() case (proto.Message): - return proto.Encode(data.(proto.Message)) + return proto.Encode(msg) } return nil, nil } @@ -214,12 +209,12 @@ func getHashBytes(data interface{}) []byte { if data == nil { return nil } - switch data.(type) { + switch msg := data.(type) { case (types.ImplHashBytes): - return data.(types.ImplHashBytes).Hash() + return msg.Hash() default: } - buf, err := marshal(data) + buf, err := Marshal(data) if err != nil { logger.Error().Err(err).Msg("failed to get hash bytes: marshal") return nil diff --git a/state/statebuffer_test.go b/state/statedb/statebuffer_test.go similarity index 99% rename from state/statebuffer_test.go rename to state/statedb/statebuffer_test.go index 56d65c1ae..6a2bded44 100644 --- a/state/statebuffer_test.go +++ b/state/statedb/statebuffer_test.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "testing" diff --git a/state/statedata.go b/state/statedb/statedata.go similarity index 74% rename from state/statedata.go rename to state/statedb/statedata.go index 2c4f47478..7cc791166 100644 --- a/state/statedata.go +++ b/state/statedb/statedata.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "github.com/aergoio/aergo-lib/db" @@ -13,16 +13,16 @@ func saveData(store db.DB, key []byte, data interface{}) error { } var err error var raw []byte - switch data.(type) { + switch msg := data.(type) { case ([]byte): - raw = data.([]byte) + raw = msg case proto.Message: - raw, err = proto.Encode(data.(proto.Message)) + raw, err = proto.Encode(msg) if err != nil { return err } default: - raw, err = gob.Encode(data) + raw, err = gob.Encode(msg) if err != nil { return err } @@ -41,13 +41,13 @@ func loadData(store db.DB, key []byte, data interface{}) error { return nil } var err error - switch data.(type) { + switch msg := data.(type) { case *[]byte: - *(data).(*[]byte) = raw + *msg = raw case proto.Message: - err = proto.Decode(raw, data.(proto.Message)) + err = proto.Decode(raw, msg) default: - err = gob.Decode(raw, data) + err = gob.Decode(raw, msg) } return err } @@ -57,7 +57,7 @@ func (states *StateDB) loadStateData(key []byte) (*types.State, error) { return nil, errLoadStateData } data := &types.State{} - if err := loadData(states.store, key, data); err != nil { + if err := loadData(states.Store, key, data); err != nil { return nil, err } return data, nil diff --git a/state/statedata_test.go b/state/statedb/statedata_test.go similarity index 61% rename from state/statedata_test.go rename to state/statedb/statedata_test.go index 4dac84cd9..ce70775eb 100644 --- a/state/statedata_test.go +++ b/state/statedb/statedata_test.go @@ -1,8 +1,11 @@ -package state +package statedb import ( + "os" "testing" + "github.com/aergoio/aergo-lib/db" + "github.com/aergoio/aergo/v2/internal/common" "github.com/stretchr/testify/assert" ) @@ -12,18 +15,33 @@ var ( testOver = []byte("test_over") ) +var ( + store db.DB + stateDB *StateDB +) + +func initTest(t *testing.T) { + dbPath := common.PathMkdirAll("test", StateName) + store = db.NewDB(db.ImplType(db.BadgerImpl), dbPath) + stateDB = NewStateDB(store, nil, false) +} +func deinitTest() { + store.Close() + stateDB = nil + _ = os.RemoveAll("test") +} func TestStateDataBasic(t *testing.T) { initTest(t) defer deinitTest() // save data - if err := saveData(stateDB.store, testKey, testData); err != nil { + if err := saveData(store, testKey, testData); err != nil { t.Errorf("failed to save data: %v", err.Error()) } // load data data := []byte{} - if err := loadData(stateDB.store, testKey, &data); err != nil { + if err := loadData(store, testKey, &data); err != nil { t.Errorf("failed to load data: %v", err.Error()) } assert.NotNil(t, data) @@ -37,7 +55,7 @@ func TestStateDataNil(t *testing.T) { // load data before saving var data interface{} assert.Nil(t, data) - if err := loadData(stateDB.store, testKey, &data); err != nil { + if err := loadData(store, testKey, &data); err != nil { t.Errorf("failed to load data: %v", err.Error()) } assert.Nil(t, data) @@ -49,13 +67,13 @@ func TestStateDataEmpty(t *testing.T) { // save empty data var testEmpty []byte - if err := saveData(stateDB.store, testKey, testEmpty); err != nil { + if err := saveData(store, testKey, testEmpty); err != nil { t.Errorf("failed to save nil data: %v", err.Error()) } // load empty data data := []byte{} - if err := loadData(stateDB.store, testKey, &data); err != nil { + if err := loadData(store, testKey, &data); err != nil { t.Errorf("failed to load data: %v", err.Error()) } assert.NotNil(t, data) @@ -67,18 +85,18 @@ func TestStateDataOverwrite(t *testing.T) { defer deinitTest() // save data - if err := saveData(stateDB.store, testKey, testData); err != nil { + if err := saveData(store, testKey, testData); err != nil { t.Errorf("failed to save data: %v", err.Error()) } // save another data to same key - if err := saveData(stateDB.store, testKey, testOver); err != nil { + if err := saveData(store, testKey, testOver); err != nil { t.Errorf("failed to overwrite data: %v", err.Error()) } // load data data := []byte{} - if err := loadData(stateDB.store, testKey, &data); err != nil { + if err := loadData(store, testKey, &data); err != nil { t.Errorf("failed to load data: %v", err.Error()) } assert.NotNil(t, data) diff --git a/state/statedb.go b/state/statedb/statedb.go similarity index 65% rename from state/statedb.go rename to state/statedb/statedb.go index eefd82874..9c65e520c 100644 --- a/state/statedb.go +++ b/state/statedb/statedb.go @@ -3,12 +3,12 @@ * @copyright defined in aergo/LICENSE.txt */ -package state +package statedb import ( "bytes" + "encoding/json" "errors" - "fmt" "math/big" "sync" @@ -18,25 +18,26 @@ import ( "github.com/aergoio/aergo/v2/internal/enc/base58" "github.com/aergoio/aergo/v2/pkg/trie" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/dbkey" ) const ( - stateName = "state" - stateLatest = stateName + ".latest" + StateName = "state" + StateLatest = StateName + ".latest" ) var ( - stateMarker = []byte{0x54, 0x45} // marker: tail end + StateMarker = []byte{0x54, 0x45} // marker: tail end ) var ( - logger = log.NewLogger(stateName) + logger = log.NewLogger(StateName) ) var ( - emptyHashID = types.HashID{} - emptyBlockID = types.BlockID{} - emptyAccountID = types.AccountID{} + EmptyHashID = types.HashID{} + EmptyBlockID = types.BlockID{} + EmptyAccountID = types.AccountID{} ) var ( @@ -58,22 +59,21 @@ var ( // StateDB manages trie of states type StateDB struct { lock sync.RWMutex - buffer *stateBuffer - cache *storageCache - trie *trie.Trie - store db.DB - batchtx db.Transaction - testmode bool + Buffer *stateBuffer + Cache *storageCache + Trie *trie.Trie + Store db.DB + Testmode bool } // NewStateDB craete StateDB instance func NewStateDB(dbstore db.DB, root []byte, test bool) *StateDB { sdb := StateDB{ - buffer: newStateBuffer(), - cache: newStorageCache(), - trie: trie.NewTrie(root, common.Hasher, dbstore), - store: dbstore, - testmode: test, + Buffer: newStateBuffer(), + Cache: newStorageCache(), + Trie: trie.NewTrie(root, common.Hasher, dbstore), + Store: dbstore, + Testmode: test, } return &sdb } @@ -83,14 +83,14 @@ func (states *StateDB) Clone() *StateDB { states.lock.RLock() defer states.lock.RUnlock() - return NewStateDB(states.store, states.GetRoot(), states.testmode) + return NewStateDB(states.Store, states.GetRoot(), states.Testmode) } // GetRoot returns root hash of trie func (states *StateDB) GetRoot() []byte { states.lock.RLock() defer states.lock.RUnlock() - return states.trie.Root + return states.Trie.Root } // SetRoot updates root node of trie as a given root hash @@ -98,9 +98,9 @@ func (states *StateDB) SetRoot(root []byte) error { states.lock.Lock() defer states.lock.Unlock() // update root node - states.trie.Root = root + states.Trie.Root = root // reset buffer - return states.buffer.reset() + return states.Buffer.reset() } // LoadCache reads first layer of trie given root hash @@ -109,12 +109,12 @@ func (states *StateDB) LoadCache(root []byte) error { states.lock.Lock() defer states.lock.Unlock() // update root node and load cache - err := states.trie.LoadCache(root) + err := states.Trie.LoadCache(root) if err != nil { return err } // reset buffer - return states.buffer.reset() + return states.Buffer.reset() } // Revert rollbacks trie to previous root hash @@ -133,20 +133,20 @@ func (states *StateDB) Revert(root types.HashID) error { // just update root node as targetRoot. // revert trie consumes unnecessarily long time. - states.trie.Root = root.Bytes() + states.Trie.Root = root.Bytes() // reset buffer - return states.buffer.reset() + return states.Buffer.reset() } // PutState puts account id and its state into state buffer. func (states *StateDB) PutState(id types.AccountID, state *types.State) error { states.lock.Lock() defer states.lock.Unlock() - if id == emptyAccountID { + if id == EmptyAccountID { return errPutState } - states.buffer.put(newValueEntry(types.HashID(id), state)) + states.Buffer.put(newValueEntry(types.HashID(id), state)) return nil } @@ -158,7 +158,7 @@ func (states *StateDB) GetAccountState(id types.AccountID) (*types.State, error) return nil, err } if st == nil { - if states.testmode { + if states.Testmode { amount := new(big.Int).Add(types.StakingMinimum, types.StakingMinimum) return &types.State{Balance: amount.Bytes()}, nil } @@ -167,158 +167,12 @@ func (states *StateDB) GetAccountState(id types.AccountID) (*types.State, error) return st, nil } -type V struct { - sdb *StateDB - id []byte - aid types.AccountID - oldV *types.State - newV *types.State - newOne bool - deploy int8 - buffer *stateBuffer -} - -const ( - deployFlag = 0x01 << iota - redeployFlag -) - -func (v *V) ID() []byte { - if len(v.id) < types.AddressLength { - v.id = types.AddressPadding(v.id) - } - return v.id -} - -func (v *V) AccountID() types.AccountID { - return v.aid -} - -func (v *V) State() *types.State { - return v.newV -} - -func (v *V) SetNonce(nonce uint64) { - v.newV.Nonce = nonce -} - -func (v *V) Balance() *big.Int { - return new(big.Int).SetBytes(v.newV.Balance) -} - -func (v *V) AddBalance(amount *big.Int) { - balance := new(big.Int).SetBytes(v.newV.Balance) - v.newV.Balance = new(big.Int).Add(balance, amount).Bytes() -} - -func (v *V) SubBalance(amount *big.Int) { - balance := new(big.Int).SetBytes(v.newV.Balance) - v.newV.Balance = new(big.Int).Sub(balance, amount).Bytes() -} - -func (v *V) RP() uint64 { - return v.newV.SqlRecoveryPoint -} - -func (v *V) IsNew() bool { - return v.newOne -} - -func (v *V) IsContract() bool { - return len(v.State().CodeHash) > 0 -} - -func (v *V) IsDeploy() bool { - return v.deploy&deployFlag != 0 -} - -func (v *V) SetRedeploy() { - v.deploy = deployFlag | redeployFlag -} - -func (v *V) IsRedeploy() bool { - return v.deploy&redeployFlag != 0 -} - -func (v *V) Reset() { - *v.newV = types.State(*v.oldV) -} - -func (v *V) PutState() error { - return v.sdb.PutState(v.aid, v.newV) -} - -func (states *StateDB) CreateAccountStateV(id []byte) (*V, error) { - v, err := states.GetAccountStateV(id) - if err != nil { - return nil, err - } - if !v.newOne { - return nil, fmt.Errorf("account(%s) aleardy exists", types.EncodeAddress(v.ID())) - } - v.newV.SqlRecoveryPoint = 1 - v.deploy = deployFlag - return v, nil -} - -func (states *StateDB) GetAccountStateV(id []byte) (*V, error) { - aid := types.ToAccountID(id) - st, err := states.GetState(aid) - if err != nil { - return nil, err - } - if st == nil { - if states.testmode { - amount := new(big.Int).Add(types.StakingMinimum, types.StakingMinimum) - return &V{ - sdb: states, - id: id, - aid: aid, - oldV: &types.State{Balance: amount.Bytes()}, - newV: &types.State{Balance: amount.Bytes()}, - newOne: true, - }, nil - } - return &V{ - sdb: states, - id: id, - aid: aid, - oldV: &types.State{}, - newV: &types.State{}, - newOne: true, - }, nil - } - newV := new(types.State) - *newV = types.State(*st) - return &V{ - sdb: states, - id: id, - aid: aid, - oldV: st, - newV: newV, - }, nil -} - -func (states *StateDB) InitAccountStateV(id []byte, old *types.State, new *types.State) *V { - return &V{ - sdb: states, - id: id, - aid: types.ToAccountID(id), - oldV: old, - newV: new, - } -} - -func (v *V) ClearAid() { - v.aid = emptyAccountID -} - // GetState gets state of account id from state buffer and trie. // nil value is returned when there is no state corresponding to account id. func (states *StateDB) GetState(id types.AccountID) (*types.State, error) { states.lock.RLock() defer states.lock.RUnlock() - if id == emptyAccountID { + if id == EmptyAccountID { return nil, errGetState } return states.getState(id) @@ -328,7 +182,7 @@ func (states *StateDB) GetState(id types.AccountID) (*types.State, error) { // nil value is returned when there is no state corresponding to account id. func (states *StateDB) getState(id types.AccountID) (*types.State, error) { // get state from buffer - if entry := states.buffer.get(types.HashID(id)); entry != nil { + if entry := states.Buffer.get(types.HashID(id)); entry != nil { return entry.Value().(*types.State), nil } // get state from trie @@ -338,11 +192,11 @@ func (states *StateDB) getState(id types.AccountID) (*types.State, error) { // getTrieState gets state of account id from trie. // nil value is returned when there is no state corresponding to account id. func (states *StateDB) getTrieState(id types.AccountID) (*types.State, error) { - key, err := states.trie.Get(id[:]) + key, err := states.Trie.Get(id[:]) if err != nil { return nil, err } - if key == nil || len(key) == 0 { + if len(key) == 0 { return nil, nil } return states.loadStateData(key) @@ -359,19 +213,19 @@ func (states *StateDB) TrieQuery(id []byte, root []byte, compressed bool) ([]byt if len(root) != 0 { if compressed { - bitmap, ap, height, isIncluded, proofKey, proofVal, err = states.trie.MerkleProofCompressedR(id, root) + bitmap, ap, height, isIncluded, proofKey, proofVal, err = states.Trie.MerkleProofCompressedR(id, root) } else { // Get the state and proof of the account for a past state - ap, isIncluded, proofKey, proofVal, err = states.trie.MerkleProofR(id, root) + ap, isIncluded, proofKey, proofVal, err = states.Trie.MerkleProofR(id, root) } } else { // Get the state and proof of the account at the latest trie // The wallet should check that state hashes to proofVal and verify the audit path, // The returned proofVal shouldn't be trusted by the wallet, it is used to proove non inclusion if compressed { - bitmap, ap, height, isIncluded, proofKey, proofVal, err = states.trie.MerkleProofCompressed(id) + bitmap, ap, height, isIncluded, proofKey, proofVal, err = states.Trie.MerkleProofCompressed(id) } else { - ap, isIncluded, proofKey, proofVal, err = states.trie.MerkleProof(id) + ap, isIncluded, proofKey, proofVal, err = states.Trie.MerkleProof(id) } } return bitmap, ap, height, isIncluded, proofKey, proofVal, err @@ -386,7 +240,7 @@ func (states *StateDB) GetVarAndProof(id []byte, root []byte, compressed bool) ( } if isIncluded { value = []byte{} - if err := loadData(states.store, dbKey, &value); err != nil { + if err := loadData(states.Store, dbKey, &value); err != nil { return nil, err } // proofKey and proofVal are only not nil for prooving exclusion with another leaf on the path @@ -442,14 +296,14 @@ type Snapshot int func (states *StateDB) Snapshot() Snapshot { states.lock.RLock() defer states.lock.RUnlock() - return Snapshot(states.buffer.snapshot()) + return Snapshot(states.Buffer.snapshot()) } // Rollback discards changes of state buffer to revision number func (states *StateDB) Rollback(revision Snapshot) error { states.lock.Lock() defer states.lock.Unlock() - return states.buffer.rollback(int(revision)) + return states.Buffer.rollback(int(revision)) } // Update applies changes of state buffer to trie @@ -469,33 +323,33 @@ func (states *StateDB) update() error { return err } // export buffer and update to trie - if err := states.buffer.updateTrie(states.trie); err != nil { + if err := states.Buffer.updateTrie(states.Trie); err != nil { return err } return nil } func (states *StateDB) updateStorage() error { - before := states.buffer.snapshot() - for id, storage := range states.cache.storages { + before := states.Buffer.snapshot() + for id, storage := range states.Cache.storages { // update storage if err := storage.update(); err != nil { - states.buffer.rollback(before) + states.Buffer.rollback(before) return err } // update state if storage root changed if storage.isDirty() { st, err := states.getState(id) if err != nil { - states.buffer.rollback(before) + states.Buffer.rollback(before) return err } if st == nil { st = &types.State{} } // put state with storage root - st.StorageRoot = storage.trie.Root - states.buffer.put(newValueEntry(types.HashID(id), st)) + st.StorageRoot = storage.Trie.Root + states.Buffer.put(newValueEntry(types.HashID(id), st)) } } return nil @@ -506,8 +360,8 @@ func (states *StateDB) Commit() error { states.lock.Lock() defer states.lock.Unlock() - bulk := states.store.NewBulk() - for _, storage := range states.cache.storages { + bulk := states.Store.NewBulk() + for _, storage := range states.Cache.storages { // stage changes if err := storage.stage(bulk); err != nil { bulk.DiscardLast() @@ -524,14 +378,14 @@ func (states *StateDB) Commit() error { func (states *StateDB) stage(txn trie.DbTx) error { // stage trie and buffer - states.trie.StageUpdates(txn) - if err := states.buffer.stage(txn); err != nil { + states.Trie.StageUpdates(txn) + if err := states.Buffer.stage(txn); err != nil { return err } // set marker states.setMarker(txn) // reset buffer - if err := states.buffer.reset(); err != nil { + if err := states.Buffer.reset(); err != nil { return err } return nil @@ -539,11 +393,11 @@ func (states *StateDB) stage(txn trie.DbTx) error { // setMarker store the marker that represents finalization of the state root. func (states *StateDB) setMarker(txn trie.DbTx) { - if states.trie.Root == nil { + if states.Trie.Root == nil { return } // logger.Debug().Str("stateRoot", enc.ToString(states.trie.Root)).Msg("setMarker") - txn.Set(common.Hasher(states.trie.Root), stateMarker) + txn.Set(common.Hasher(states.Trie.Root), StateMarker) } // HasMarker represents that the state root is finalized or not. @@ -551,10 +405,32 @@ func (states *StateDB) HasMarker(root []byte) bool { if root == nil { return false } - marker := states.store.Get(common.Hasher(root)) - if marker != nil && bytes.Equal(marker, stateMarker) { + marker := states.Store.Get(common.Hasher(root)) + if marker != nil && bytes.Equal(marker, StateMarker) { // logger.Debug().Str("stateRoot", enc.ToString(root)).Str("marker", hex.HexEncode(marker)).Msg("IsMarked") return true } return false } + +func (states *StateDB) IsLegacyTrieKey() bool { + root := states.GetRoot() + if len(root) == 0 { + return false + } + + prefixRoot := states.Store.Get(dbkey.Trie(root)) + legacyRoot := states.Store.Get(root) + if len(prefixRoot) == 0 && len(legacyRoot) > 0 { + return true + } + return false +} + +func (sdb *StateDB) Dump() ([]byte, error) { + dump, err := sdb.RawDump() + if err != nil { + return nil, err + } + return json.MarshalIndent(dump, "", "\t") +} diff --git a/state/statedb_test.go b/state/statedb/statedb_test.go similarity index 86% rename from state/statedb_test.go rename to state/statedb/statedb_test.go index daef8fa70..2f81a13d7 100644 --- a/state/statedb_test.go +++ b/state/statedb/statedb_test.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "bytes" @@ -13,7 +13,7 @@ var ( testAccount = types.ToAccountID([]byte("test_address")) //testRoot, _ = enc.ToBytes("5eGvHsNc5526JBqd8FhKFrtti2fT7xiCyB6rJXt9egFc") testRoot = []byte{0xde, 0xf0, 0x85, 0x93, 0x70, 0x51, 0x4d, 0x51, 0x36, 0x82, 0x9e, 0xeb, 0x4a, 0xd1, 0x6, 0x57, 0x7c, 0xd1, 0xc8, 0x52, 0xc, 0xcb, 0x74, 0xb2, 0xa6, 0x4b, 0xf0, 0x34, 0xc6, 0xf4, 0x5d, 0x80} - testStates = []types.State{ + testStates = []*types.State{ {Nonce: 1, Balance: new(big.Int).SetUint64(100).Bytes()}, {Nonce: 2, Balance: new(big.Int).SetUint64(200).Bytes()}, {Nonce: 3, Balance: new(big.Int).SetUint64(300).Bytes()}, @@ -22,7 +22,7 @@ var ( } //testSecondRoot, _ = enc.ToBytes("GGKZy5XqNPU1VWYspHPwEtm8hnZX2yhcP236ztKf6Pif") testSecondRoot = []byte{0x66, 0xf9, 0x19, 0x2, 0x91, 0xe6, 0xb5, 0x74, 0x3, 0x69, 0x1e, 0x86, 0x87, 0x22, 0x78, 0x1f, 0x4, 0xc3, 0x67, 0x5, 0xf1, 0xb6, 0xce, 0x4b, 0x63, 0x61, 0x6, 0x2c, 0x24, 0xb1, 0xe7, 0xda} - testSecondStates = []types.State{ + testSecondStates = []*types.State{ {Nonce: 6, Balance: new(big.Int).SetUint64(600).Bytes()}, {Nonce: 7, Balance: new(big.Int).SetUint64(700).Bytes()}, {Nonce: 8, Balance: new(big.Int).SetUint64(800).Bytes()}, @@ -62,7 +62,7 @@ func TestStateDBPutState(t *testing.T) { defer deinitTest() // put state - err := stateDB.PutState(testAccount, &testStates[0]) + err := stateDB.PutState(testAccount, testStates[0]) if err != nil { t.Errorf("failed to put state: %v", err.Error()) } @@ -73,7 +73,7 @@ func TestStateDBPutState(t *testing.T) { t.Errorf("failed to get account state: %v", err.Error()) } assert.NotNil(t, st) - assert.True(t, stateEquals(&testStates[0], st)) + assert.True(t, stateEquals(testStates[0], st)) } func TestStateDBRollback(t *testing.T) { @@ -83,11 +83,11 @@ func TestStateDBRollback(t *testing.T) { // put states initialRevision := stateDB.Snapshot() for _, v := range testStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } revision := stateDB.Snapshot() for _, v := range testSecondStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } // get state @@ -96,7 +96,7 @@ func TestStateDBRollback(t *testing.T) { t.Errorf("failed to get account state: %v", err.Error()) } assert.NotNil(t, st) - assert.True(t, stateEquals(&testSecondStates[2], st)) + assert.True(t, stateEquals(testSecondStates[2], st)) // rollback to snapshot err = stateDB.Rollback(revision) @@ -108,7 +108,7 @@ func TestStateDBRollback(t *testing.T) { t.Errorf("failed to get account state: %v", err.Error()) } assert.NotNil(t, st) - assert.True(t, stateEquals(&testStates[4], st)) + assert.True(t, stateEquals(testStates[4], st)) // rollback to initial revision snapshot err = stateDB.Rollback(initialRevision) @@ -129,7 +129,7 @@ func TestStateDBUpdateAndCommit(t *testing.T) { assert.Nil(t, stateDB.GetRoot()) for _, v := range testStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } assert.Nil(t, stateDB.GetRoot()) @@ -154,7 +154,7 @@ func TestStateDBSetRoot(t *testing.T) { // put states assert.Nil(t, stateDB.GetRoot()) for _, v := range testStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } _ = stateDB.Update() _ = stateDB.Commit() @@ -162,7 +162,7 @@ func TestStateDBSetRoot(t *testing.T) { // put additional states for _, v := range testSecondStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } _ = stateDB.Update() _ = stateDB.Commit() @@ -170,7 +170,7 @@ func TestStateDBSetRoot(t *testing.T) { // get state st, _ := stateDB.GetAccountState(testAccount) - assert.True(t, stateEquals(&testSecondStates[2], st)) + assert.True(t, stateEquals(testSecondStates[2], st)) // set root err := stateDB.SetRoot(testRoot) @@ -184,7 +184,7 @@ func TestStateDBSetRoot(t *testing.T) { if err != nil { t.Errorf("failed to get account state: %v", err.Error()) } - assert.True(t, stateEquals(&testStates[4], st)) + assert.True(t, stateEquals(testStates[4], st)) } func TestStateDBParallel(t *testing.T) { @@ -194,7 +194,7 @@ func TestStateDBParallel(t *testing.T) { // put states assert.Nil(t, stateDB.GetRoot()) for _, v := range testStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } _ = stateDB.Update() _ = stateDB.Commit() @@ -202,7 +202,7 @@ func TestStateDBParallel(t *testing.T) { // put additional states for _, v := range testSecondStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } _ = stateDB.Update() _ = stateDB.Commit() @@ -210,10 +210,10 @@ func TestStateDBParallel(t *testing.T) { // get state st, _ := stateDB.GetAccountState(testAccount) - assert.True(t, stateEquals(&testSecondStates[2], st)) + assert.True(t, stateEquals(testSecondStates[2], st)) // open another statedb with root hash of previous state - anotherStateDB := chainStateDB.OpenNewStateDB(testRoot) + anotherStateDB := NewStateDB(store, testRoot, false) assert.Equal(t, testRoot, anotherStateDB.GetRoot()) assert.Equal(t, testSecondRoot, stateDB.GetRoot()) @@ -222,14 +222,14 @@ func TestStateDBParallel(t *testing.T) { if err != nil { t.Errorf("failed to get state: %v", err.Error()) } - assert.True(t, stateEquals(&testSecondStates[2], st1)) + assert.True(t, stateEquals(testSecondStates[2], st1)) // get state from another statedb st2, err := anotherStateDB.GetAccountState(testAccount) if err != nil { t.Errorf("failed to get state: %v", err.Error()) } - assert.True(t, stateEquals(&testStates[4], st2)) + assert.True(t, stateEquals(testStates[4], st2)) } func TestStateDBMarker(t *testing.T) { @@ -238,7 +238,7 @@ func TestStateDBMarker(t *testing.T) { assert.Nil(t, stateDB.GetRoot()) for _, v := range testStates { - _ = stateDB.PutState(testAccount, &v) + _ = stateDB.PutState(testAccount, v) } _ = stateDB.Update() _ = stateDB.Commit() diff --git a/state/storage.go b/state/statedb/storage.go similarity index 70% rename from state/storage.go rename to state/statedb/storage.go index 03d8dd433..a578dc069 100644 --- a/state/storage.go +++ b/state/statedb/storage.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "bytes" @@ -26,22 +26,22 @@ func newStorageCache() *storageCache { } } -func (cache *storageCache) snapshot() map[types.AccountID]int { +func (cache *storageCache) Snapshot() map[types.AccountID]int { cache.lock.RLock() defer cache.lock.RUnlock() result := make(map[types.AccountID]int) for aid, bs := range cache.storages { - result[aid] = bs.buffer.snapshot() + result[aid] = bs.Buffer.snapshot() } return result } -func (cache *storageCache) rollback(snap map[types.AccountID]int) error { +func (cache *storageCache) Rollback(snap map[types.AccountID]int) error { cache.lock.Lock() defer cache.lock.Unlock() for aid, bs := range cache.storages { if rev, ok := snap[aid]; ok { - if err := bs.buffer.rollback(rev); err != nil { + if err := bs.Buffer.rollback(rev); err != nil { return err } } else { @@ -66,50 +66,50 @@ func (cache *storageCache) put(key types.AccountID, storage *bufferedStorage) { } type bufferedStorage struct { - buffer *stateBuffer - trie *trie.Trie + Buffer *stateBuffer + Trie *trie.Trie dirty bool } func newBufferedStorage(root []byte, store db.DB) *bufferedStorage { return &bufferedStorage{ - buffer: newStateBuffer(), - trie: trie.NewTrie(root, common.Hasher, store), + Buffer: newStateBuffer(), + Trie: trie.NewTrie(root, common.Hasher, store), dirty: false, } } func (storage *bufferedStorage) has(key types.HashID, lookupTrie bool) bool { - if storage.buffer.has(key) { + if storage.Buffer.has(key) { return true } if lookupTrie { - if buf, _ := storage.trie.Get(key.Bytes()); buf != nil { + if buf, _ := storage.Trie.Get(key.Bytes()); buf != nil { return true } } return false } func (storage *bufferedStorage) get(key types.HashID) entry { - return storage.buffer.get(key) + return storage.Buffer.get(key) } func (storage *bufferedStorage) put(et entry) { - storage.buffer.put(et) + storage.Buffer.put(et) } func (storage *bufferedStorage) checkpoint(revision int) { - storage.buffer.put(newMetaEntry(checkpointKey, revision)) + storage.Buffer.put(newMetaEntry(checkpointKey, revision)) } func (storage *bufferedStorage) rollback(revision int) { - checkpoints, ok := storage.buffer.indexes[checkpointKey] + checkpoints, ok := storage.Buffer.indexes[checkpointKey] if !ok { // do nothing return } it := checkpoints.iter() for rev := it(); rev >= 0; rev = it() { - et := storage.buffer.entries[rev] + et := storage.Buffer.entries[rev] if et == nil { continue } @@ -124,18 +124,18 @@ func (storage *bufferedStorage) rollback(revision int) { if val < revision { break } - storage.buffer.rollback(rev) + storage.Buffer.rollback(rev) } } func (storage *bufferedStorage) update() error { - before := storage.trie.Root - if err := storage.buffer.updateTrie(storage.trie); err != nil { + before := storage.Trie.Root + if err := storage.Buffer.updateTrie(storage.Trie); err != nil { return err } - if !bytes.Equal(before, storage.trie.Root) { + if !bytes.Equal(before, storage.Trie.Root) { logger.Debug().Str("before", base58.Encode(before)). - Str("after", base58.Encode(storage.trie.Root)).Msg("Changed storage trie root") + Str("after", base58.Encode(storage.Trie.Root)).Msg("Changed storage trie root") storage.dirty = true } return nil @@ -146,11 +146,11 @@ func (storage *bufferedStorage) isDirty() bool { } func (storage *bufferedStorage) stage(txn trie.DbTx) error { - storage.trie.StageUpdates(txn) - if err := storage.buffer.stage(txn); err != nil { + storage.Trie.StageUpdates(txn) + if err := storage.Buffer.stage(txn); err != nil { return err } - if err := storage.buffer.reset(); err != nil { + if err := storage.Buffer.reset(); err != nil { return err } return nil diff --git a/state/storage_test.go b/state/statedb/storage_test.go similarity index 98% rename from state/storage_test.go rename to state/statedb/storage_test.go index 8cf79325d..afa614886 100644 --- a/state/storage_test.go +++ b/state/statedb/storage_test.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "testing" @@ -100,7 +100,7 @@ func TestStorageHasKey(t *testing.T) { // update storage and reset buffer err := storage.update() assert.NoError(t, err, "failed to update storage") - err = storage.buffer.reset() + err = storage.Buffer.reset() assert.NoError(t, err, "failed to reset buffer") // after update and reset assert.False(t, storage.has(v1, false)) // buffer doesn't have key @@ -114,7 +114,7 @@ func TestStorageHasKey(t *testing.T) { // update storage and reset buffer err = storage.update() assert.NoError(t, err, "failed to update storage") - err = storage.buffer.reset() + err = storage.Buffer.reset() assert.NoError(t, err, "failed to reset buffer") // after update and reset assert.False(t, storage.has(v1, false)) // buffer doesn't have key diff --git a/state/util.go b/state/statedb/util.go similarity index 98% rename from state/util.go rename to state/statedb/util.go index 84650e4ab..72384e919 100644 --- a/state/util.go +++ b/state/statedb/util.go @@ -1,4 +1,4 @@ -package state +package statedb type stack []int diff --git a/state/util_test.go b/state/statedb/util_test.go similarity index 99% rename from state/util_test.go rename to state/statedb/util_test.go index 322379153..5c30f179b 100644 --- a/state/util_test.go +++ b/state/statedb/util_test.go @@ -1,4 +1,4 @@ -package state +package statedb import ( "testing" diff --git a/tests/common.sh b/tests/common.sh index 07f573593..8ac62a70e 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -38,7 +38,10 @@ wait_version() { output=$(../bin/aergocli blockchain 2>/dev/null) # check if 'output' is non-empty and starts with '{' if [[ -n "$output" ]] && [[ "${output:0:1}" == "{" ]]; then - cur_version=$(echo "$output" | jq .ChainInfo.Chainid.Version | sed 's/"//g') + cur_version=$(echo "$output" | jq .chainInfo.id.version | sed 's/"//g') + if [ "$cur_version" == "null" ]; then + cur_version=0 + fi else cur_version=0 fi diff --git a/tests/run_tests.sh b/tests/run_tests.sh index f212dac71..bebe6e279 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -76,7 +76,7 @@ function set_version() { echo "---------------------------------" echo "now test hardfork version $version" # get the current block number / height - block_no=$(../bin/aergocli blockchain | jq .Height | sed 's/"//g') + block_no=$(../bin/aergocli blockchain | jq .height | sed 's/"//g') # increment 2 numbers block_no=$((block_no+2)) # terminate the server process(es) diff --git a/tests/test-gas-per-function-v2.sh b/tests/test-gas-per-function-v2.sh index 40091b577..1706f8ec2 100755 --- a/tests/test-gas-per-function-v2.sh +++ b/tests/test-gas-per-function-v2.sh @@ -30,7 +30,7 @@ status=$(cat receipt.json | jq .status | sed 's/"//g') ret=$(cat receipt.json | jq .ret | sed 's/"//g') assert_equals "$status" "SUCCESS" -assert_equals "$ret" "{}" +assert_equals "$ret" "" echo "-- get account's nonce --" diff --git a/tests/test-gas-per-function-v3.sh b/tests/test-gas-per-function-v3.sh index 35c39caff..7e4c6b432 100755 --- a/tests/test-gas-per-function-v3.sh +++ b/tests/test-gas-per-function-v3.sh @@ -30,7 +30,7 @@ status=$(cat receipt.json | jq .status | sed 's/"//g') ret=$(cat receipt.json | jq .ret | sed 's/"//g') assert_equals "$status" "SUCCESS" -assert_equals "$ret" "{}" +assert_equals "$ret" "" echo "-- get account's nonce --" diff --git a/tools/mpdumpdiag/main.go b/tools/mpdumpdiag/main.go index 56d5608e9..5d997e3dd 100644 --- a/tools/mpdumpdiag/main.go +++ b/tools/mpdumpdiag/main.go @@ -5,12 +5,11 @@ import ( "encoding/binary" "encoding/json" "io" - "io/ioutil" "os" - "github.com/aergoio/aergo/v2/cmd/aergocli/util" "github.com/aergoio/aergo/v2/internal/enc/proto" "github.com/aergoio/aergo/v2/types" + "github.com/aergoio/aergo/v2/types/jsonrpc" "github.com/spf13/cobra" ) @@ -53,7 +52,7 @@ func runPrintCmd(cmd *cobra.Command, args []string) { reader := bufio.NewReader(file) var count int - var out []*util.InOutTx + var out []*jsonrpc.InOutTx for { buf := types.Tx{} byteInt := make([]byte, 4) @@ -83,7 +82,7 @@ func runPrintCmd(cmd *cobra.Command, args []string) { count++ //mp.put(types.NewTransaction(&buf)) // nolint: errcheck - out = append(out, util.ConvTx(types.NewTransaction(&buf).GetTx())) + out = append(out, jsonrpc.ConvTx(types.NewTransaction(&buf).GetTx(), jsonrpc.Base58)) } b, e := json.MarshalIndent(out, "", " ") if e == nil { @@ -105,11 +104,11 @@ func runGenCmd(cmd *cobra.Command, args []string) { writer := bufio.NewWriter(file) defer writer.Flush() //nolint: errcheck - b, err := ioutil.ReadFile(args[0]) + b, err := os.ReadFile(args[0]) if err != nil { cmd.Println("error: failed to read source file", err.Error()) } - txlist, err := util.ParseBase58Tx(b) + txlist, err := jsonrpc.ParseBase58Tx(b) for _, v := range txlist { var total_data []byte data, err := proto.Encode(v) diff --git a/types/blockchain.go b/types/blockchain.go index e4905dc54..372bbdf3b 100644 --- a/types/blockchain.go +++ b/types/blockchain.go @@ -433,16 +433,7 @@ func (block *Block) VerifySign() (valid bool, err error) { // BPID returns its Block Producer's ID from block. func (block *Block) BPID() (id PeerID, err error) { - var pubKey crypto.PubKey - if pubKey, err = crypto.UnmarshalPublicKey(block.Header.PubKey); err != nil { - return PeerID(""), err - } - - if id, err = IDFromPublicKey(pubKey); err != nil { - return PeerID(""), err - } - - return + return block.Header.BPID() } // BpID2Str returns its Block Producer's ID in base64 format. @@ -481,7 +472,7 @@ func (block *Block) PrevID() string { func (block *Block) setPubKey(pubKey crypto.PubKey) error { var pk []byte var err error - if pk, err = pubKey.Bytes(); err != nil { + if pk, err = crypto.MarshalPublicKey(pubKey); err != nil { return err } block.Header.PubKey = pk @@ -559,15 +550,15 @@ func (tx *Tx) Clone() *Tx { } body := &TxBody{ Nonce: tx.Body.Nonce, - Account: Clone(tx.Body.Account).([]byte), - Recipient: Clone(tx.Body.Recipient).([]byte), - Amount: Clone(tx.Body.Amount).([]byte), - Payload: Clone(tx.Body.Payload).([]byte), + Account: tx.Body.Account, + Recipient: tx.Body.Recipient, + Amount: tx.Body.Amount, + Payload: tx.Body.Payload, GasLimit: tx.Body.GasLimit, - GasPrice: Clone(tx.Body.GasPrice).([]byte), + GasPrice: tx.Body.GasPrice, Type: tx.Body.Type, - ChainIdHash: Clone(tx.Body.ChainIdHash).([]byte), - Sign: Clone(tx.Body.Sign).([]byte), + ChainIdHash: tx.Body.ChainIdHash, + Sign: tx.Body.Sign, } res := &Tx{ Body: body, @@ -680,6 +671,17 @@ func (b *BlockHeaderInfo) ChainIdHash() []byte { return common.Hasher(b.ChainId) } +func (bh *BlockHeader) BPID() (id PeerID, err error) { + var pubKey crypto.PubKey + if pubKey, err = crypto.UnmarshalPublicKey(bh.PubKey); err != nil { + return PeerID(""), err + } + if id, err = IDFromPublicKey(pubKey); err != nil { + return PeerID(""), err + } + return +} + // HasFunction returns if a function with the given name exists in the ABI definition func (abi *ABI) HasFunction(name string) bool { for _, fn := range abi.Functions { diff --git a/types/dbkey/key.go b/types/dbkey/key.go index 56b2331ea..b25be2cdc 100644 --- a/types/dbkey/key.go +++ b/types/dbkey/key.go @@ -7,6 +7,13 @@ import ( "github.com/aergoio/aergo/v2/types" ) +//---------------------------------------------------------------------------------// +// state trie + +func Trie(key []byte) []byte { + return append([]byte(triePrefix), key...) +} + //---------------------------------------------------------------------------------// // chain diff --git a/types/dbkey/schema.go b/types/dbkey/schema.go index 0713bb825..311481c58 100644 --- a/types/dbkey/schema.go +++ b/types/dbkey/schema.go @@ -1,6 +1,11 @@ // package dbkey contains a key prefix collection of low level database accessors. package dbkey +// state trie +const ( + triePrefix = "s" +) + // chain const ( receiptsPrefix = "r" diff --git a/types/jsonrpc/account.go b/types/jsonrpc/account.go new file mode 100644 index 000000000..0d817310f --- /dev/null +++ b/types/jsonrpc/account.go @@ -0,0 +1,128 @@ +package jsonrpc + +import ( + "math/big" + + "github.com/aergoio/aergo/v2/types" +) + +func ConvAccount(msg *types.Account) *InOutAccount { + if msg == nil { + return nil + } + + a := &InOutAccount{} + a.Address = types.EncodeAddress(msg.GetAddress()) + + return a +} + +type InOutAccount struct { + Address string `json:"address,omitempty"` +} + +func ConvAccounts(msg *types.AccountList) *InOutAccountList { + if msg == nil { + return nil + } + + al := &InOutAccountList{} + al.Accounts = make([]*InOutAccount, len(msg.Accounts)) + for i, account := range msg.Accounts { + al.Accounts[i] = ConvAccount(account) + } + + return al +} + +type InOutAccountList struct { + Accounts []*InOutAccount `json:"accounts,omitempty"` +} + +func ConvState(msg *types.State) *InOutState { + if msg == nil { + return nil + } + + s := &InOutState{} + s.Nonce = msg.GetNonce() + s.Balance = new(big.Int).SetBytes(msg.GetBalance()).String() + if len(msg.GetCodeHash()) > 0 { + s.IsContract = true + } else { + s.IsContract = false + } + + return s +} + +type InOutState struct { + Nonce uint64 `json:"nonce"` + Balance string `json:"balance"` + Account string `json:"account,omitempty"` + Stake string `json:"stake,omitempty"` + Total string `json:"total,omitempty"` + VotingPower string `json:"votingpower,omitempty"` + When uint64 `json:"when,omitempty"` + NextAction uint64 `json:"nextaction,omitempty"` + IsContract bool `json:"isContract"` +} + +func ConvStateAndPoof(msg *types.AccountProof) *InOutStateAndPoof { + if msg == nil { + return nil + } + + ap := &InOutStateAndPoof{} + ap.Nonce = msg.GetState().GetNonce() + ap.Balance = new(big.Int).SetBytes(msg.State.GetBalance()).String() + ap.Included = msg.GetInclusion() + ap.MerkleProofLength = len(msg.GetAuditPath()) + ap.Height = msg.GetHeight() + + return ap +} + +type InOutStateAndPoof struct { + Nonce uint64 `json:"nonce"` + Balance string `json:"balance"` + Account string `json:"account,omitempty"` + Included bool `json:"included,omitempty"` + MerkleProofLength int `json:"merkle proof length,omitempty"` + Height uint32 `json:"height,omitempty"` +} + +func ConvNameInfo(msg *types.NameInfo) *InOutNameInfo { + if msg == nil { + return nil + } + + ni := &InOutNameInfo{} + ni.Name = msg.Name.Name + ni.Owner = types.EncodeAddress(msg.Owner) + ni.Destination = types.EncodeAddress(msg.Destination) + + return ni +} + +type InOutNameInfo struct { + Name string `json:"name"` + Owner string `json:"owner"` + Destination string `json:"destination"` +} + +func ConvBalance(msg *types.State) *InOutBalance { + if msg == nil { + return nil + } + + b := &InOutBalance{} + state := ConvState(msg) + b.Balance = state.Balance + + return b +} + +type InOutBalance struct { + Balance string `json:"balance"` +} diff --git a/types/jsonrpc/block.go b/types/jsonrpc/block.go new file mode 100644 index 000000000..3a084e628 --- /dev/null +++ b/types/jsonrpc/block.go @@ -0,0 +1,200 @@ +package jsonrpc + +import ( + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ConvBlock(msg *types.Block) *InOutBlock { + if msg == nil { + return nil + } + + b := &InOutBlock{} + b.Hash = base58.Encode(msg.Hash) + b.Header = ConvBlockHeader(msg.Header) + b.Body = ConvBlockBody(msg.Body) + return b +} + +type InOutBlock struct { + Hash string `json:"hash,omitempty"` + Header *InOutBlockHeader `json:"header,omitempty"` + Body *InOutBlockBody `json:"body,omitempty"` +} + +func ConvBlockHeader(msg *types.BlockHeader) *InOutBlockHeader { + if msg == nil { + return nil + } + + bh := &InOutBlockHeader{} + bh.ChainID = base58.Encode(msg.GetChainID()) + bh.Version = types.DecodeChainIdVersion(msg.GetChainID()) + bh.PrevBlockHash = base58.Encode(msg.GetPrevBlockHash()) + bh.BlockNo = msg.GetBlockNo() + bh.Timestamp = msg.GetTimestamp() + bh.BlockRootHash = base58.Encode(msg.GetBlocksRootHash()) + bh.TxsRootHash = base58.Encode(msg.GetTxsRootHash()) + bh.ReceiptsRootHash = base58.Encode(msg.GetReceiptsRootHash()) + bh.Confirms = msg.GetConfirms() + bh.PubKey = base58.Encode(msg.GetPubKey()) + if bpid, err := msg.BPID(); err == nil { + bh.PeerID = bpid.String() + } + bh.Sign = base58.Encode(msg.GetSign()) + if msg.GetCoinbaseAccount() != nil { + bh.CoinbaseAccount = types.EncodeAddress(msg.GetCoinbaseAccount()) + } + if consensus := msg.GetConsensus(); consensus != nil { + bh.Consensus = types.EncodeAddress(consensus) + } + return bh +} + +type InOutBlockHeader struct { + ChainID string `json:"chainID,omitempty"` + Version int32 `json:"version,omitempty"` + PrevBlockHash string `json:"prevBlockHash,omitempty"` + BlockNo uint64 `json:"blockNo,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` + BlockRootHash string `json:"blockRootHash,omitempty"` + TxsRootHash string `json:"txsRootHash,omitempty"` + ReceiptsRootHash string `json:"receiptsRootHash,omitempty"` + Confirms uint64 `json:"confirms,omitempty"` + PubKey string `json:"pubKey,omitempty"` + PeerID string `json:"peerID,omitempty"` + CoinbaseAccount string `json:"coinbaseAccount,omitempty"` + Sign string `json:"sign,omitempty"` + Consensus string `json:"consensus,omitempty"` +} + +func ConvBlockBody(msg *types.BlockBody) *InOutBlockBody { + if msg == nil { + return nil + } + + bb := &InOutBlockBody{} + bb.Txs = make([]*InOutTx, len(msg.Txs)) + for i, tx := range msg.Txs { + bb.Txs[i] = ConvTx(tx, Base58) + } + return bb +} + +type InOutBlockBody struct { + Txs []*InOutTx `json:"txs,omitempty"` +} + +func ConvBlockIdx(msg *types.NewBlockNotice) *InOutBlockIdx { + if msg == nil { + return nil + } + + return &InOutBlockIdx{ + BlockHash: base58.Encode(msg.GetBlockHash()), + BlockNo: msg.GetBlockNo(), + } +} + +type InOutBlockIdx struct { + BlockHash string `json:"blockHash,omitempty"` + BlockNo uint64 `json:"blockNo,omitempty"` +} + +func ConvBlockHeaderList(msg *types.BlockHeaderList) *InOutBlockHeaderList { + if msg == nil { + return nil + } + + b := &InOutBlockHeaderList{} + b.Blocks = make([]*InOutBlock, len(msg.Blocks)) + for i, block := range msg.Blocks { + b.Blocks[i] = ConvBlock(block) + } + return b +} + +type InOutBlockHeaderList struct { + Blocks []*InOutBlock `json:"blocks"` +} + +func ConvBlockBodyPaged(msg *types.BlockBodyPaged) *InOutBlockBodyPaged { + if msg == nil { + return nil + } + + bbp := &InOutBlockBodyPaged{} + body := ConvBlockBody(msg.GetBody()) + bbp.Txs = body.Txs + bbp.Total = msg.GetTotal() + bbp.Offset = msg.GetOffset() + bbp.Size = msg.GetSize() + + return bbp +} + +type InOutBlockBodyPaged struct { + Total uint32 `json:"total,omitempty"` + Offset uint32 `json:"offset,omitempty"` + Size uint32 `json:"size,omitempty"` + Txs []*InOutTx `json:"txs,omitempty"` +} + +func ConvBlockMetadata(msg *types.BlockMetadata) *InOutBlockMetadata { + if msg == nil { + return nil + } + + bbm := &InOutBlockMetadata{} + bbm.Hash = base58.Encode(msg.Hash) + bbm.Header = ConvBlockHeader(msg.GetHeader()) + bbm.Txcount = msg.GetTxcount() + bbm.Size = msg.GetSize() + + return bbm +} + +type InOutBlockMetadata struct { + Hash string `json:"hash,omitempty"` + Header *InOutBlockHeader `json:"header,omitempty"` + Txcount int32 `json:"txcount,omitempty"` + Size int64 `json:"size,omitempty"` +} + +func ConvListBlockMetadata(msg *types.BlockMetadataList) *InOutBlockMetadataList { + if msg == nil { + return nil + } + + bbml := &InOutBlockMetadataList{} + bbml.Blocks = make([]*InOutBlockMetadata, len(msg.Blocks)) + for i, block := range msg.Blocks { + bbml.Blocks[i] = ConvBlockMetadata(block) + } + + return bbml +} + +type InOutBlockMetadataList struct { + Blocks []*InOutBlockMetadata `json:"blocks,omitempty"` +} + +func ConvBlockTransactionCount(msg *types.Block) *InOutBlockTransactionCount { + if msg == nil { + return nil + } + + btc := &InOutBlockTransactionCount{} + if msg.Body.Txs != nil { + btc.Count = len(msg.Body.Txs) + } else { + btc.Count = 0 + } + + return btc +} + +type InOutBlockTransactionCount struct { + Count int `json:"count"` +} diff --git a/types/jsonrpc/block_test.go b/types/jsonrpc/block_test.go new file mode 100644 index 000000000..b9e02887d --- /dev/null +++ b/types/jsonrpc/block_test.go @@ -0,0 +1,112 @@ +package jsonrpc + +import ( + "testing" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" + "github.com/stretchr/testify/assert" +) + +const ( + testAccountBase58 = "AmMW2bVcfroiuV4Bvy56op5zzqn42xgrLCwSxMka23K75yTBmudz" + testRecipientBase58 = "AmMW2bVcfroiuV4Bvy56op5zzqn42xgrLCwSxMka23K75yTBmudz" + testPayloadBase58 = "525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi" + testBlockHashBase58 = "5bSKqpcWnMgrr1GhU1Ed5yHajRC4WwZEZYxFtw3fVBmq" + testTxHashBase58 = "5v1hmuTmDbS744oHMVJdFtb5LNPn4wbAtcK1HtveUAz" + testChainIdHashBase58 = "73zLfCHqvPk1oRvV7VeTgXx6XcL3Sat9u" // 73zLfCHqvPk1oRvV7VeTgXx6XcL3Sat9u + testSignBase58 = "3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzL" +) + +func TestConvBlock(t *testing.T) { + account, err := types.DecodeAddress(testAccountBase58) + assert.NoError(t, err, "should be decode account") + + blockHash, err := base58.Decode(testBlockHashBase58) + assert.NoError(t, err, "should be decode block hash") + + testBlock := &types.Block{ + Hash: blockHash, + Header: &types.BlockHeader{ + CoinbaseAccount: account, + }, + Body: &types.BlockBody{ + Txs: []*types.Tx{}, + }, + } + result := ConvBlock(nil) + assert.Empty(t, result, "failed to convert nil") + + result = ConvBlock(testBlock) + assert.Equal(t, testBlockHashBase58, result.Hash, "failed to convert block hash") + t.Log(ConvBlock(testBlock)) +} + +func TestConvBlockHeader(t *testing.T) { + decodeBlockHash, err := base58.Decode(testBlockHashBase58) + assert.NoError(t, err, "should be decode block hash") + + for _, test := range []struct { + types *types.BlockHeader + inout *InOutBlockHeader + }{ + {&types.BlockHeader{ + ChainID: []byte{0x00, 0x00, 0x00, 0x00}, + PrevBlockHash: decodeBlockHash, + BlockNo: 1, + Timestamp: 1600000000, + Confirms: 1, + }, &InOutBlockHeader{ + ChainID: base58.Encode([]byte{0x00, 0x00, 0x00, 0x00}), + Version: types.DecodeChainIdVersion([]byte{0x00, 0x00, 0x00, 0x00}), + PrevBlockHash: testBlockHashBase58, + BlockNo: 1, + Timestamp: 1600000000, + Confirms: 1, + }}, + } { + result := ConvBlockHeader(test.types) + assert.Equal(t, test.inout, result, "failed to convert block header") + } +} + +func TestConvBlockBody(t *testing.T) { + account, err := types.DecodeAddress(testAccountBase58) + assert.NoError(t, err, "should be decode account") + + recipient, err := types.DecodeAddress(testRecipientBase58) + assert.NoError(t, err, "should be decode recipient") + + payload, err := base58.Decode(testPayloadBase58) + assert.NoError(t, err, "should be decode payload") + + for _, test := range []struct { + types *types.BlockBody + inout *InOutBlockBody + }{ + {&types.BlockBody{ + Txs: []*types.Tx{ + { + Body: &types.TxBody{ + Account: account, + Recipient: recipient, + Payload: payload, + }, + }, + }, + }, &InOutBlockBody{ + Txs: []*InOutTx{ + { + Body: &InOutTxBody{ + Account: testAccountBase58, + Recipient: testRecipientBase58, + Payload: testPayloadBase58, + }, + }, + }, + }}, + } { + result := ConvBlockBody(test.types) + assert.Equal(t, test.inout, result, "failed to convert block body") + } +} diff --git a/types/jsonrpc/chainInfo.go b/types/jsonrpc/chainInfo.go new file mode 100644 index 000000000..447f44a37 --- /dev/null +++ b/types/jsonrpc/chainInfo.go @@ -0,0 +1,147 @@ +package jsonrpc + +import ( + "encoding/json" + "math/big" + + "github.com/aergoio/aergo/v2/consensus" + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/internal/enc/hex" + "github.com/aergoio/aergo/v2/types" +) + +func ConvChainInfo(msg *types.ChainInfo) *InOutChainInfo { + if msg == nil { + return nil + } + + ci := &InOutChainInfo{} + ci.Id = ConvChainId(msg.Id) + ci.BpNumber = msg.BpNumber + ci.MaxBlockSize = msg.Maxblocksize + ci.MaxTokens = new(big.Int).SetBytes(msg.Maxtokens).String() + if ci.Id != nil && consensus.IsDposName(ci.Id.Consensus) { + ci.StakingMinimum = new(big.Int).SetBytes(msg.Stakingminimum).String() + ci.Totalstaking = new(big.Int).SetBytes(msg.Totalstaking).String() + } + ci.GasPrice = new(big.Int).SetBytes(msg.Gasprice).String() + ci.NamePrice = new(big.Int).SetBytes(msg.Nameprice).String() + ci.TotalVotingPower = new(big.Int).SetBytes(msg.Totalvotingpower).String() + ci.VotingReward = new(big.Int).SetBytes(msg.Votingreward).String() + return ci +} + +type InOutChainInfo struct { + Id *InOutChainId `json:"id,omitempty"` + BpNumber uint32 `json:"bpNumber,omitempty"` + MaxBlockSize uint64 `json:"maxblocksize,omitempty"` + MaxTokens string `json:"maxtokens,omitempty"` + StakingMinimum string `json:"stakingminimum,omitempty"` + Totalstaking string `json:"totalstaking,omitempty"` + GasPrice string `json:"gasprice,omitempty"` + NamePrice string `json:"nameprice,omitempty"` + TotalVotingPower string `json:"totalvotingpower,omitempty"` + VotingReward string `json:"votingreward,omitempty"` +} + +func ConvChainId(msg *types.ChainId) *InOutChainId { + if msg == nil { + return nil + } + return &InOutChainId{ + Magic: msg.Magic, + Public: msg.Public, + Mainnet: msg.Mainnet, + Consensus: msg.Consensus, + Version: msg.Version, + } +} + +type InOutChainId struct { + Magic string `json:"magic,omitempty"` + Public bool `json:"public,omitempty"` + Mainnet bool `json:"mainnet,omitempty"` + Consensus string `json:"consensus,omitempty"` + Version int32 `json:"version,omitempty"` +} + +func ConvBlockchainStatus(msg *types.BlockchainStatus) *InOutBlockchainStatus { + if msg == nil { + return nil + } + + bs := &InOutBlockchainStatus{} + bs.Hash = base58.Encode(msg.BestBlockHash) + bs.Height = msg.BestHeight + bs.ChainIdHash = base58.Encode(msg.BestChainIdHash) + + toJRM := func(s string) *json.RawMessage { + if len(s) > 0 { + m := json.RawMessage(s) + return &m + } + return nil + } + bs.ConsensusInfo = toJRM(msg.ConsensusInfo) + if msg.ChainInfo != nil { + bs.ChainInfo = ConvChainInfo(msg.ChainInfo) + } + return bs +} + +func ConvHexBlockchainStatus(msg *types.BlockchainStatus) *InOutBlockchainStatus { + if msg == nil { + return nil + } + + bs := &InOutBlockchainStatus{} + bs.Hash = hex.Encode(msg.BestBlockHash) + bs.Height = msg.BestHeight + bs.ChainIdHash = hex.Encode(msg.BestChainIdHash) + return bs +} + +type InOutBlockchainStatus struct { + Hash string `json:"hash"` + Height uint64 `json:"height"` + ConsensusInfo *json.RawMessage `json:"consensusInfo,omitempty"` + ChainIdHash string `json:"chainIdHash"` + ChainStat *json.RawMessage `json:"chainStat,omitempty"` + ChainInfo *InOutChainInfo `json:"chainInfo,omitempty"` +} + +func ConvChainStat(msg *types.ChainStats) *InOutChainStats { + if msg == nil { + return nil + } + + cs := &InOutChainStats{} + _ = json.Unmarshal([]byte(msg.GetReport()), &cs.Report) + return cs +} + +type InOutChainStats struct { + Report interface{} `json:"report,omitempty"` +} + +func ConvConsensusInfo(msg *types.ConsensusInfo) *InOutConsensusInfo { + if msg == nil { + return nil + } + + ci := &InOutConsensusInfo{} + ci.Type = msg.GetType() + _ = json.Unmarshal([]byte(msg.GetInfo()), &ci.Info) + + ci.Bps = make([]interface{}, len(msg.Bps)) + for i, bps := range msg.Bps { + _ = json.Unmarshal([]byte(bps), &ci.Bps[i]) + } + return ci +} + +type InOutConsensusInfo struct { + Type string `json:"type,omitempty"` + Info interface{} `json:"info,omitempty"` + Bps []interface{} `json:"bps,omitempty"` +} diff --git a/types/jsonrpc/chaininfo_test.go b/types/jsonrpc/chaininfo_test.go new file mode 100644 index 000000000..6552ddb89 --- /dev/null +++ b/types/jsonrpc/chaininfo_test.go @@ -0,0 +1,145 @@ +package jsonrpc + +import ( + "encoding/hex" + "encoding/json" + "testing" + + "github.com/aergoio/aergo/v2/chain" + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" + "github.com/stretchr/testify/require" +) + +func TestConvChainInfo(t *testing.T) { + var ( + maxAer = types.MaxAER + stakingMinimum = types.StakingMinimum + stakingTotal = types.NewAmount(10000000, types.Aergo) + gasPrice = types.NewAmount(5, types.Aergo) + namePrice = types.NewAmount(1, types.Aergo) + totalVotingPower = types.NewAmount(10000000, types.Aergo) + votingReward = types.NewAmount(1, types.Aergo) + ) + + for _, test := range []struct { + types *types.ChainInfo + inout *InOutChainInfo + }{ + {&types.ChainInfo{ // not dpos - ignore stakingminimum, totalstaking + BpNumber: 13, + Maxblocksize: uint64(chain.MaxBlockSize()), + Maxtokens: maxAer.Bytes(), + Stakingminimum: stakingMinimum.Bytes(), + Totalstaking: stakingTotal.Bytes(), + Gasprice: gasPrice.Bytes(), + Nameprice: namePrice.Bytes(), + Totalvotingpower: totalVotingPower.Bytes(), + Votingreward: votingReward.Bytes(), + }, &InOutChainInfo{ + BpNumber: 13, + MaxBlockSize: uint64(chain.MaxBlockSize()), + MaxTokens: maxAer.String(), + GasPrice: gasPrice.String(), + NamePrice: namePrice.String(), + TotalVotingPower: totalVotingPower.String(), + VotingReward: votingReward.String(), + }}, + + {&types.ChainInfo{ // dpos + Id: &types.ChainId{Consensus: "dpos"}, + BpNumber: 13, + Maxblocksize: uint64(chain.MaxBlockSize()), + Maxtokens: maxAer.Bytes(), + Stakingminimum: stakingMinimum.Bytes(), + Totalstaking: stakingTotal.Bytes(), + Gasprice: gasPrice.Bytes(), + Nameprice: namePrice.Bytes(), + Totalvotingpower: totalVotingPower.Bytes(), + Votingreward: votingReward.Bytes(), + }, &InOutChainInfo{ + Id: &InOutChainId{Consensus: "dpos"}, + BpNumber: 13, + MaxBlockSize: uint64(chain.MaxBlockSize()), + MaxTokens: maxAer.String(), + StakingMinimum: stakingMinimum.String(), + Totalstaking: stakingTotal.String(), + GasPrice: gasPrice.String(), + NamePrice: namePrice.String(), + TotalVotingPower: totalVotingPower.String(), + VotingReward: votingReward.String(), + }}, + } { + require.Equal(t, test.inout, ConvChainInfo(test.types)) + } +} + +func TestConvChainId(t *testing.T) { + for _, test := range []struct { + types *types.ChainId + inout *InOutChainId + }{ + {&types.ChainId{ + Version: 0, + Magic: "dev.chain", + Public: false, + Mainnet: false, + Consensus: "sbp", + }, &InOutChainId{ + Version: 0, + Magic: "dev.chain", + Public: false, + Mainnet: false, + Consensus: "sbp", + }}, + } { + require.Equal(t, test.inout, ConvChainId(test.types)) + } +} + +func TestConvBlockchainStatus(t *testing.T) { + blockHash, err := base58.Decode(testBlockHashBase58) + require.NoError(t, err) + + chainIdHash, err := base58.Decode(testChainIdHashBase58) + require.NoError(t, err) + + consensusInfo := "consensus info" + jsonConsensusInfo := json.RawMessage(consensusInfo) + + for _, test := range []struct { + format string // base58 or hex + types *types.BlockchainStatus + inout *InOutBlockchainStatus + }{ + {"base58", &types.BlockchainStatus{ + BestBlockHash: blockHash, + BestHeight: 1, + ConsensusInfo: consensusInfo, + BestChainIdHash: chainIdHash, + }, &InOutBlockchainStatus{ + Hash: testBlockHashBase58, + Height: 1, + ChainIdHash: testChainIdHashBase58, + ConsensusInfo: &jsonConsensusInfo, + }}, + {"hex", &types.BlockchainStatus{ + BestBlockHash: blockHash, + BestHeight: 1, + ConsensusInfo: consensusInfo, + BestChainIdHash: chainIdHash, + }, &InOutBlockchainStatus{ + Hash: hex.EncodeToString(blockHash), + Height: 1, + ChainIdHash: hex.EncodeToString(chainIdHash), + }}, + } { + if test.format == "base58" { + require.Equal(t, test.inout, ConvBlockchainStatus(test.types)) + } else if test.format == "hex" { + require.Equal(t, test.inout, ConvHexBlockchainStatus(test.types)) + } else { + require.Fail(t, "invalid format", test.format) + } + } +} diff --git a/types/jsonrpc/commit.go b/types/jsonrpc/commit.go new file mode 100644 index 000000000..d93a6603f --- /dev/null +++ b/types/jsonrpc/commit.go @@ -0,0 +1,44 @@ +package jsonrpc + +import ( + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ConvCommitResultList(msg *types.CommitResultList) *InOutCommitResultList { + if msg == nil { + return nil + } + c := &InOutCommitResultList{} + c.Results = make([]*InOutCommitResult, len(msg.Results)) + for i, result := range msg.Results { + c.Results[i] = ConvCommitResult(result) + } + return c +} + +type InOutCommitResultList struct { + Results []*InOutCommitResult `json:"results,omitempty"` +} + +func ConvCommitResult(msg *types.CommitResult) *InOutCommitResult { + cr := &InOutCommitResult{ + Hash: base58.Encode(msg.Hash), + Error: msg.Error, + Detail: msg.Detail, + } + + status, err := types.CommitStatus_name[int32(msg.Error)] + if err && msg.Error != types.CommitStatus_TX_OK { + cr.Message = status + } + + return cr +} + +type InOutCommitResult struct { + Hash string `json:"hash,omitempty"` + Error types.CommitStatus `json:"error,omitempty"` + Detail string `json:"detail,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/types/jsonrpc/encoding.go b/types/jsonrpc/encoding.go new file mode 100644 index 000000000..ca9e91fd9 --- /dev/null +++ b/types/jsonrpc/encoding.go @@ -0,0 +1,22 @@ +package jsonrpc + +import ( + "encoding/json" + "fmt" +) + +type EncodingType int + +const ( + Raw EncodingType = 0 + iota + Base58 +) + +func MarshalJSON(i interface{}) string { + jsonout, err := json.MarshalIndent(i, "", " ") + if err != nil { + fmt.Printf("Failed: %s\n", err.Error()) + return "" + } + return string(jsonout) +} diff --git a/types/jsonrpc/enterprise.go b/types/jsonrpc/enterprise.go new file mode 100644 index 000000000..cb6d0d815 --- /dev/null +++ b/types/jsonrpc/enterprise.go @@ -0,0 +1,112 @@ +package jsonrpc + +import ( + "strings" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ConvEnterpriseTxStatus(msg *types.EnterpriseTxStatus) *InOutEnterpriseTxStatus { + if msg == nil { + return nil + } + + ets := &InOutEnterpriseTxStatus{} + ets.Status = msg.Status + ets.Ret = msg.Ret + ets.CCStatus = ConvChangeClusterStatus(msg.CCStatus) + return ets +} + +type InOutEnterpriseTxStatus struct { + Status string `json:"status"` + Ret string `json:"ret"` + CCStatus *InOutChangeClusterStatus `json:"change_cluster,omitempty"` +} + +func ConvChangeClusterStatus(msg *types.ChangeClusterStatus) *InOutChangeClusterStatus { + if msg == nil { + return nil + } + + ccs := &InOutChangeClusterStatus{} + ccs.State = msg.State + ccs.Error = msg.Error + ccs.Members = make([]*InOutMemberAttr, len(msg.Members)) + for i, m := range msg.Members { + ccs.Members[i] = ConvMemberAttr(m) + } + return ccs +} + +type InOutChangeClusterStatus struct { + State string `json:"status"` + Error string `json:"error"` + Members []*InOutMemberAttr `json:"members"` +} + +func ConvMemberAttr(msg *types.MemberAttr) *InOutMemberAttr { + if msg == nil { + return nil + } + return &InOutMemberAttr{ + ID: msg.ID, + Name: msg.Name, + Address: msg.Address, + PeerID: base58.Encode(msg.PeerID), + } +} + +type InOutMemberAttr struct { + ID uint64 `json:"ID,omitempty"` + Name string `json:"name,omitempty"` + Address string `json:"address,omitempty"` + PeerID string `json:"peerID,omitempty"` +} + +func ConvEnterpriseConfig(msg *types.EnterpriseConfig) *InOutEnterpriseConfig { + if msg == nil { + return nil + } + ec := &InOutEnterpriseConfig{} + ec.Key = msg.GetKey() + + ec.Values = make([]string, len(msg.Values)) + for i, value := range msg.Values { + ec.Values[i] = value + } + + if strings.ToUpper(ec.Key) != "PERMISSIONS" { + ec.On = msg.On + } + return ec +} + +type InOutEnterpriseConfig struct { + Key string `json:"key,omitempty"` + On bool `json:"on"` + Values []string `json:"values"` +} + +func ConvConfChangeProgress(msg *types.ConfChangeProgress) *InOutConfChangeProgress { + if msg == nil { + return nil + } + ccp := &InOutConfChangeProgress{} + + ccp.State = int32(msg.GetState()) + ccp.Err = msg.GetErr() + ccp.Members = make([]*InOutMemberAttr, len(msg.Members)) + for i, m := range msg.Members { + ccp.Members[i] = ConvMemberAttr(m) + } + + return ccp +} + +type InOutConfChangeProgress struct { + State int32 `json:"state,omitempty"` + Err string `json:"err,omitempty"` + Members []*InOutMemberAttr `json:"members,omitempty"` +} diff --git a/types/jsonrpc/governance.go b/types/jsonrpc/governance.go new file mode 100644 index 000000000..a9b462afa --- /dev/null +++ b/types/jsonrpc/governance.go @@ -0,0 +1,104 @@ +package jsonrpc + +import ( + "math/big" + "strings" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ConvInOutAccountVoteInfo(msg *types.AccountVoteInfo) *InOutAccountVoteInfo { + if msg == nil { + return nil + } + + avi := &InOutAccountVoteInfo{} + avi.Staking = ConvStaking(msg.Staking) + avi.Voting = make([]*InOutVoteInfo, len(msg.Voting)) + for i, v := range msg.Voting { + avi.Voting[i] = ConvVoteInfo(v) + } + return avi +} + +type InOutAccountVoteInfo struct { + Staking *InOutStaking `json:"staking,omitempty"` + Voting []*InOutVoteInfo `json:"voting,omitempty"` +} + +func ConvStaking(msg *types.Staking) *InOutStaking { + if msg == nil { + return nil + } + + return &InOutStaking{ + Amount: new(big.Int).SetBytes(msg.Amount).String(), + When: msg.When, + } +} + +type InOutStaking struct { + Amount string `json:"amount,omitempty"` + When uint64 `json:"when,omitempty"` +} + +func ConvVoteInfo(msg *types.VoteInfo) *InOutVoteInfo { + if msg == nil { + return nil + } + return &InOutVoteInfo{ + Id: msg.Id, + Candidates: msg.Candidates, + Amount: msg.Amount, + } +} + +type InOutVoteInfo struct { + Id string `json:"id,omitempty"` + Candidates []string `json:"candidates,omitempty"` + Amount string `json:"amount,omitempty"` +} + +func ConvVote(msg *types.Vote, id string) *InOutVote { + if msg == nil { + return nil + } + + vote := &InOutVote{ + Amount: msg.GetAmountBigInt().String(), + } + + if strings.ToLower(id) == strings.ToLower(types.OpvoteBP.ID()) { + vote.Candidate = base58.Encode(msg.Candidate) + } else { + vote.Candidate = string(msg.Candidate) + } + + return vote +} + +type InOutVote struct { + Candidate string `json:"candidate,omitempty"` + Amount string `json:"amount,omitempty"` +} + +func ConvVotes(msg *types.VoteList, id string) *InOutVotes { + if msg == nil { + return nil + } + vs := &InOutVotes{} + vs.Id = msg.GetId() + + vs.Votes = make([]*InOutVote, len(msg.Votes)) + for i, vote := range msg.Votes { + vs.Votes[i] = ConvVote(vote, id) + } + + return vs +} + +type InOutVotes struct { + Votes []*InOutVote `json:"votes,omitempty"` + Id string `json:"id,omitempty"` +} diff --git a/types/jsonrpc/node.go b/types/jsonrpc/node.go new file mode 100644 index 000000000..7dda3d641 --- /dev/null +++ b/types/jsonrpc/node.go @@ -0,0 +1,68 @@ +package jsonrpc + +import ( + "encoding/json" + + "github.com/aergoio/aergo/v2/types" +) + +func ConvNodeState(msg *types.SingleBytes) interface{} { + if msg == nil { + return nil + } + var ns interface{} + _ = json.Unmarshal(msg.GetValue(), &ns) + + return ns +} + +type InOutNodeState struct { + AccountsSvc map[string]interface{} `json:"AccountsSvc,omitempty"` + ChainSvc map[string]interface{} `json:"ChainSvc,omitempty"` + MemPoolSvc map[string]interface{} `json:"MemPoolSvc,omitempty"` + RPCSvc map[string]interface{} `json:"RPCSvc,omitempty"` + SyncerSvc map[string]interface{} `json:"SyncerSvc,omitempty"` + MapSvc map[string]interface{} `json:"mapSvc,omitempty"` + P2pSvc map[string]interface{} `json:"p2pSvc,omitempty"` +} + +func ConvConfigItem(msg *types.ConfigItem) *InOutConfigItem { + if msg == nil { + return nil + } + ci := &InOutConfigItem{} + + ci.Props = make(map[string]string) + for key, value := range msg.Props { + ci.Props[key] = value + } + return ci +} + +type InOutConfigItem struct { + Props map[string]string `json:"props,omitempty"` +} + +func ConvServerInfo(msg *types.ServerInfo) *InOutServerInfo { + if msg == nil { + return nil + } + si := &InOutServerInfo{} + + si.Status = make(map[string]string) + for key, status := range msg.Status { + si.Status[key] = status + } + + si.Config = make(map[string]*InOutConfigItem) + for key, value := range msg.Config { + si.Config[key] = ConvConfigItem(value) + } + + return si +} + +type InOutServerInfo struct { + Status map[string]string `json:"status,omitempty"` + Config map[string]*InOutConfigItem `json:"config,omitempty"` +} diff --git a/types/jsonrpc/query.go b/types/jsonrpc/query.go new file mode 100644 index 000000000..ab3c0c449 --- /dev/null +++ b/types/jsonrpc/query.go @@ -0,0 +1,58 @@ +package jsonrpc + +import ( + "github.com/aergoio/aergo/v2/types" +) + +func ConvQueryContract(msg *types.SingleBytes) *InOutQueryContract { + if msg == nil { + return nil + } + q := &InOutQueryContract{} + q.Result = string(msg.Value) + return q +} + +type InOutQueryContract struct { + Result string `json:"result"` +} + +func ConvContractVarProof(msg *types.ContractVarProof) *InOutContractVarProof { + if msg == nil { + return nil + } + ap := &InOutContractVarProof{} + ap.Value = string(msg.GetValue()) + ap.Included = msg.GetInclusion() + ap.MerkleProofLength = len(msg.GetAuditPath()) + ap.Height = msg.GetHeight() + + return ap +} + +type InOutContractVarProof struct { + Value string `json:"value,omitempty"` + Included bool `json:"included,omitempty"` + MerkleProofLength int `json:"merkleprooflength,omitempty"` + Height uint32 `json:"height,omitempty"` +} + +func ConvQueryContractState(msg *types.StateQueryProof) *InOutQueryContractState { + if msg == nil { + return nil + } + qcs := &InOutQueryContractState{} + qcs.ContractProof = ConvStateAndPoof(msg.ContractProof) + + qcs.VarProofs = make([]*InOutContractVarProof, len(msg.VarProofs)) + for i, varProof := range msg.VarProofs { + qcs.VarProofs[i] = ConvContractVarProof(varProof) + } + + return qcs +} + +type InOutQueryContractState struct { + ContractProof *InOutStateAndPoof `json:"contractProof,omitempty"` + VarProofs []*InOutContractVarProof `json:"varProofs,omitempty"` +} diff --git a/types/jsonrpc/raft.go b/types/jsonrpc/raft.go new file mode 100644 index 000000000..056783348 --- /dev/null +++ b/types/jsonrpc/raft.go @@ -0,0 +1,25 @@ +package jsonrpc + +import ( + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/etcd/raft/raftpb" +) + +func ConvConfChange(msg *raftpb.ConfChange) *InOutConfChange { + if msg == nil { + return nil + } + return &InOutConfChange{ + ID: msg.ID, + Type: msg.Type, + NodeID: msg.NodeID, + Context: base58.Encode(msg.Context), + } +} + +type InOutConfChange struct { + ID uint64 `json:"ID,omitempty"` + Type raftpb.ConfChangeType `json:"type,omitempty"` + NodeID uint64 `json:"nodeID,omitempty"` + Context string `json:"context,omitempty"` +} diff --git a/types/jsonrpc/receipt.go b/types/jsonrpc/receipt.go new file mode 100644 index 000000000..c937c3b52 --- /dev/null +++ b/types/jsonrpc/receipt.go @@ -0,0 +1,222 @@ +package jsonrpc + +import ( + "math/big" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ConvReceipt(msg *types.Receipt) *InOutReceipt { + if msg == nil { + return nil + } + r := &InOutReceipt{} + r.ContractAddress = types.EncodeAddress(msg.ContractAddress) + r.Status = msg.Status + r.Ret = msg.Ret + r.TxHash = base58.Encode(msg.TxHash) + r.FeeUsed = new(big.Int).SetBytes(msg.FeeUsed).String() + r.CumulativeFeeUsed = new(big.Int).SetBytes(msg.CumulativeFeeUsed).String() + r.Bloom = msg.Bloom + if msg.Events != nil { + r.Events = make([]*InOutEvent, len(msg.Events)) + for i, e := range msg.Events { + r.Events[i] = ConvEvent(e) + } + } + r.BlockHash = base58.Encode(msg.BlockHash) + r.BlockNo = msg.BlockNo + r.TxIndex = msg.TxIndex + r.From = types.EncodeAddress(msg.From) + r.To = types.EncodeAddress(msg.To) + r.FeeDelegation = msg.FeeDelegation + r.GasUsed = msg.GasUsed + return r +} + +type InOutReceipt struct { + ContractAddress string `json:"contractAddress"` + Status string `json:"status"` + Ret string `json:"ret"` + TxHash string `json:"txHash"` + FeeUsed string `json:"feeUsed"` + CumulativeFeeUsed string `json:"cumulativeFeeUsed,omitempty"` + Bloom []byte `json:"bloom,omitempty"` + Events []*InOutEvent `json:"events,omitempty"` + BlockHash string `json:"blockHash,omitempty"` + BlockNo uint64 `json:"blockNo,omitempty"` + TxIndex int32 `json:"txIndex,omitempty"` + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` + FeeDelegation bool `json:"feeDelegation,omitempty"` + GasUsed uint64 `json:"gasUsed,omitempty"` +} + +func ConvEvent(msg *types.Event) *InOutEvent { + if msg == nil { + return nil + } + return &InOutEvent{ + ContractAddress: types.EncodeAddress(msg.ContractAddress), + EventName: msg.EventName, + JsonArgs: msg.JsonArgs, + EventIdx: msg.EventIdx, + TxHash: base58.Encode(msg.TxHash), + BlockHash: base58.Encode(msg.BlockHash), + BlockNo: msg.BlockNo, + TxIndex: msg.TxIndex, + } +} + +type InOutEvent struct { + ContractAddress string `json:"contractAddress"` + EventName string `json:"eventName"` + JsonArgs string `json:"jsonArgs"` + EventIdx int32 `json:"eventIdx"` + TxHash string `json:"txHash"` + BlockHash string `json:"blockHash"` + BlockNo uint64 `json:"blockNo"` + TxIndex int32 `json:"txIndex"` +} + +func ConvAbi(msg *types.ABI) *InOutAbi { + if msg == nil { + return nil + } + abi := &InOutAbi{} + abi.Version = msg.Version + abi.Language = msg.Language + abi.Functions = make([]*InOutFunction, len(msg.Functions)) + for i, fn := range msg.Functions { + abi.Functions[i] = ConvFunction(fn) + } + abi.StateVariables = make([]*InOutStateVar, len(msg.StateVariables)) + for i, sv := range msg.StateVariables { + abi.StateVariables[i] = ConvStateVar(sv) + } + return abi +} + +type InOutAbi struct { + Version string `json:"version"` + Language string `json:"language"` + Functions []*InOutFunction `json:"functions"` + StateVariables []*InOutStateVar `json:"stateVariables"` +} + +func ConvFunction(msg *types.Function) *InOutFunction { + if msg == nil { + return nil + } + fn := &InOutFunction{} + fn.Name = msg.Name + fn.Arguments = make([]*InOutFunctionArgument, len(msg.Arguments)) + for i, arg := range msg.Arguments { + fn.Arguments[i] = ConvFunctionArgument(arg) + } + fn.Payable = msg.Payable + fn.View = msg.View + fn.FeeDelegation = msg.FeeDelegation + return fn +} + +type InOutFunction struct { + Name string `json:"name"` + Arguments []*InOutFunctionArgument `json:"arguments"` + Payable bool `json:"payable"` + View bool `json:"view"` + FeeDelegation bool `json:"feeDelegation"` +} + +func ConvFunctionArgument(msg *types.FnArgument) *InOutFunctionArgument { + if msg == nil { + return nil + } + return &InOutFunctionArgument{ + Name: msg.Name, + } +} + +type InOutFunctionArgument struct { + Name string `json:"name"` +} + +func ConvStateVar(msg *types.StateVar) *InOutStateVar { + if msg == nil { + return nil + } + return &InOutStateVar{ + Name: msg.Name, + Type: msg.Type, + Len: msg.Len, + } +} + +type InOutStateVar struct { + Name string `json:"name"` + Type string `json:"type"` + Len int32 `json:"len"` +} + +func ConvReceipts(msg *types.Receipts) *InOutReceipts { + if msg == nil { + return nil + } + + rs := &InOutReceipts{} + rs.BlockNo = msg.GetBlockNo() + rs.Receipts = make([]*InOutReceipt, len(msg.Get())) + for i, receipt := range msg.Get() { + rs.Receipts[i] = ConvReceipt(receipt) + } + return rs +} + +type InOutReceipts struct { + Receipts []*InOutReceipt `json:"receipts"` + BlockNo uint64 `json:"blockNo,omitempty"` +} + +func ConvReceiptsPaged(msg *types.ReceiptsPaged) *InOutReceiptsPaged { + if msg == nil { + return nil + } + + rp := &InOutReceiptsPaged{} + rp.Total = msg.GetTotal() + rp.Offset = msg.GetOffset() + rp.Size = msg.GetSize() + rp.BlockNo = msg.GetBlockNo() + rp.Receipts = make([]*InOutReceipt, len(msg.Get())) + for i, receipt := range msg.Get() { + rp.Receipts[i] = ConvReceipt(receipt) + } + + return rp +} + +type InOutReceiptsPaged struct { + Total uint32 `json:"total,omitempty"` + Offset uint32 `json:"offset,omitempty"` + Size uint32 `json:"size,omitempty"` + Receipts []*InOutReceipt `json:"receipts"` + BlockNo uint64 `json:"blockNo,omitempty"` +} + +func ConvEvents(msg *types.EventList) *InOutEventList { + if msg == nil { + return nil + } + + rs := &InOutEventList{} + rs.Events = make([]*InOutEvent, len(msg.Events)) + for i, event := range msg.Events { + rs.Events[i] = ConvEvent(event) + } + return rs +} + +type InOutEventList struct { + Events []*InOutEvent `json:"events,omitempty"` +} diff --git a/types/jsonrpc/rpc.go b/types/jsonrpc/rpc.go new file mode 100644 index 000000000..2d7ae0817 --- /dev/null +++ b/types/jsonrpc/rpc.go @@ -0,0 +1,222 @@ +package jsonrpc + +import ( + "fmt" + "strconv" + "time" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/p2p/p2putil" + "github.com/aergoio/aergo/v2/types" +) + +func ConvPeerList(msg *types.PeerList) *InOutPeerList { + if msg == nil { + return nil + } + + p := &InOutPeerList{} + p.Peers = make([]*InOutPeer, len(msg.Peers)) + for i, peer := range msg.Peers { + p.Peers[i] = ConvPeer(peer) + } + return p +} + +type InOutPeerList struct { + Peers []*InOutPeer `json:"peers"` +} + +func ConvPeer(msg *types.Peer) *InOutPeer { + if msg == nil { + return nil + } + + p := &InOutPeer{} + p.Role = msg.AcceptedRole.String() + p.Address = ConvPeerAddress(msg.GetAddress()) + p.BestBlock = ConvBlockIdx(msg.GetBestblock()) + p.LastCheck = time.Unix(0, msg.GetLashCheck()) + p.State = types.PeerState(msg.State).String() + p.Hidden = msg.Hidden + p.SelfPeer = msg.Selfpeer + if msg.Version != "" { + p.Version = msg.Version + } else { + p.Version = "(old)" + } + return p +} + +type InOutPeer struct { + Role string `json:"peerrole,omitempty"` + Address *InOutPeerAddress `json:"address,omitempty"` + BestBlock *InOutBlockIdx `json:"bestblock,omitempty"` + LastCheck time.Time `json:"lastCheck,omitempty"` + State string `json:"state,omitempty"` + Hidden bool `json:"hidden,omitempty"` + SelfPeer bool `json:"selfpeer,omitempty"` + Version string `json:"version,omitempty"` +} + +func ConvPeerAddress(msg *types.PeerAddress) *InOutPeerAddress { + if msg == nil { + return nil + } + return &InOutPeerAddress{ + Address: msg.Address, + Port: strconv.Itoa(int(msg.Port)), + PeerID: base58.Encode(msg.PeerID), + } +} + +type InOutPeerAddress struct { + Address string `json:"address,omitempty"` + Port string `json:"port,omitempty"` + PeerID string `json:"peerID,omitempty"` +} + +func ConvShortPeerList(msg *types.PeerList) *InOutShortPeerList { + if msg == nil { + return nil + } + p := &InOutShortPeerList{} + p.Peers = make([]string, len(msg.Peers)) + for i, peer := range msg.Peers { + pa := peer.Address + p.Peers[i] = fmt.Sprintf("%s;%s/%d;%s;%d", p2putil.ShortForm(types.PeerID(pa.PeerID)), pa.Address, pa.Port, peer.AcceptedRole.String(), peer.Bestblock.BlockNo) + } + return p +} + +type InOutShortPeerList struct { + Peers []string `json:"peers,omitempty"` +} + +func ConvLongPeerList(msg *types.PeerList) *InOutLongPeerList { + if msg == nil { + return nil + } + + p := &InOutLongPeerList{} + p.Peers = make([]*InOutLongPeer, len(msg.Peers)) + for i, peer := range msg.Peers { + p.Peers[i] = ConvLongPeer(peer) + } + return p +} + +type InOutLongPeerList struct { + Peers []*InOutLongPeer `json:"peers,omitempty"` +} + +func ConvLongPeer(msg *types.Peer) *InOutLongPeer { + if msg == nil { + return nil + } + + p := &InOutLongPeer{} + p.InOutPeer = ConvPeer(msg) + + p.ProducerIDs = make([]string, len(msg.Address.ProducerIDs)) + for i, pid := range msg.Address.ProducerIDs { + p.ProducerIDs[i] = base58.Encode(pid) + } + + if msg.Address.Role == types.PeerRole_Agent { + p.Certificates = make([]*InOutCert, len(msg.Certificates)) + for i, cert := range msg.Certificates { + p.Certificates[i] = &InOutCert{} + p.Certificates[i] = ConvCert(cert) + } + } + return p +} + +type InOutLongPeer struct { + *InOutPeer `json:",inline"` + ProducerIDs []string `json:"producerIDs,omitempty"` + Certificates []*InOutCert `json:"certificates,omitempty"` +} + +func ConvCert(msg *types.AgentCertificate) *InOutCert { + if msg == nil { + return nil + } + + c := &InOutCert{} + c.CertVersion = msg.CertVersion + c.ProducerID = base58.Encode(msg.BPID) + c.AgentID = base58.Encode(msg.AgentID) + c.CreateTime = time.Unix(0, msg.CreateTime) + c.ExpireTime = time.Unix(0, msg.ExpireTime) + c.Addresses = []string{} + for _, ad := range msg.AgentAddress { + c.Addresses = append(c.Addresses, string(ad)) + } + return c +} + +type InOutCert struct { + CertVersion uint32 `json:"certVersion,omitempty"` + ProducerID string `json:"producerID,omitempty"` + CreateTime time.Time `json:"createTime,omitempty"` + ExpireTime time.Time `json:"expireTime,omitempty"` + AgentID string `json:"agentID,omitempty"` + Addresses []string `json:"addresses,omitempty"` +} + +func ConvMetrics(msg *types.Metrics) *InOutMetrics { + if msg == nil { + return nil + } + + m := &InOutMetrics{} + m.Peers = make([]*InOutPeerMetric, len(msg.Peers)) + for i, peer := range msg.Peers { + m.Peers[i] = ConvPeerMetric(peer) + } + return m +} + +type InOutMetrics struct { + Peers []*InOutPeerMetric `json:"peers,omitempty"` +} + +func ConvPeerMetric(msg *types.PeerMetric) *InOutPeerMetric { + if msg == nil { + return nil + } + + return &InOutPeerMetric{ + PeerID: base58.Encode(msg.PeerID), + SumIn: msg.SumIn, + AvrIn: msg.AvrIn, + SumOut: msg.SumOut, + AvrOut: msg.AvrOut, + } +} + +type InOutPeerMetric struct { + PeerID string `json:"peerID,omitempty"` + SumIn int64 `json:"sumIn,omitempty"` + AvrIn int64 `json:"avrIn,omitempty"` + SumOut int64 `json:"sumOut,omitempty"` + AvrOut int64 `json:"avrOut,omitempty"` +} + +func ConvBLConfEntries(msg *types.BLConfEntries) *InOutBLConfEntries { + if msg == nil { + return nil + } + + return &InOutBLConfEntries{ + Enabled: msg.Enabled, + Entries: msg.Entries, + } +} + +type InOutBLConfEntries struct { + Enabled bool `json:"enabled,omitempty"` + Entries []string `json:"entries,omitempty"` +} diff --git a/types/jsonrpc/tx.go b/types/jsonrpc/tx.go new file mode 100644 index 000000000..105a57415 --- /dev/null +++ b/types/jsonrpc/tx.go @@ -0,0 +1,250 @@ +package jsonrpc + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" +) + +func ParseBase58Tx(jsonTx []byte) ([]*types.Tx, error) { + var inputlist []InOutTx + err := json.Unmarshal([]byte(jsonTx), &inputlist) + if err != nil { + var input InOutTx + err = json.Unmarshal([]byte(jsonTx), &input) + if err != nil { + return nil, err + } + inputlist = append(inputlist, input) + } + txs := make([]*types.Tx, len(inputlist)) + for i, in := range inputlist { + tx := &types.Tx{Body: &types.TxBody{}} + if in.Hash != "" { + tx.Hash, err = base58.Decode(in.Hash) + if err != nil { + return nil, err + } + } + tx.Body, err = ParseTxBody(in.Body) + if err != nil { + return nil, err + } + txs[i] = tx + } + + return txs, nil +} + +func ParseBase58TxBody(jsonTx []byte) (*types.TxBody, error) { + in := &InOutTxBody{} + err := json.Unmarshal(jsonTx, in) + if err != nil { + return nil, err + } + + body, err := ParseTxBody(in) + if err != nil { + return nil, err + } + + return body, nil +} + +//-----------------------------------------------------------------------// +// + +func ConvTx(msg *types.Tx, payloadType EncodingType) (tx *InOutTx) { + if msg == nil { + return nil + } + tx = &InOutTx{} + tx.Body = &InOutTxBody{} + if msg == nil { + return + } + tx.Hash = base58.Encode(msg.Hash) + if msg.Body != nil { + tx.Body = ConvTxBody(msg.Body, payloadType) + } + + return tx +} + +type InOutTx struct { + Hash string `json:"hash,omitempty"` + Body *InOutTxBody `json:"body,omitempty"` +} + +func (t *InOutTx) String() string { + return MarshalJSON(t) +} + +func ConvTxBody(msg *types.TxBody, payloadType EncodingType) *InOutTxBody { + if msg == nil { + return nil + } + + tb := &InOutTxBody{} + tb.Nonce = msg.Nonce + if msg.Account != nil { + tb.Account = types.EncodeAddress(msg.Account) + } + if msg.Recipient != nil { + tb.Recipient = types.EncodeAddress(msg.Recipient) + } + if msg.Amount != nil { + tb.Amount = new(big.Int).SetBytes(msg.Amount).String() + } + switch payloadType { + case Raw: + tb.Payload = string(msg.Payload) + case Base58: + tb.Payload = base58.Encode(msg.Payload) + } + tb.GasLimit = msg.GasLimit + if msg.GasPrice != nil { + tb.GasPrice = new(big.Int).SetBytes(msg.GasPrice).String() + } + tb.ChainIdHash = base58.Encode(msg.ChainIdHash) + tb.Sign = base58.Encode(msg.Sign) + tb.Type = msg.Type + return tb +} + +func ParseTxBody(tb *InOutTxBody) (msg *types.TxBody, err error) { + if tb == nil { + return nil, errors.New("tx body is empty") + } + msg = &types.TxBody{} + + msg.Nonce = tb.Nonce + if tb.Account != "" { + msg.Account, err = types.DecodeAddress(tb.Account) + if err != nil { + return nil, err + } + } + if tb.Recipient != "" { + msg.Recipient, err = types.DecodeAddress(tb.Recipient) + if err != nil { + return nil, err + } + } + if tb.Amount != "" { + amount, err := ParseUnit(tb.Amount) + if err != nil { + return nil, err + } + msg.Amount = amount.Bytes() + } + if tb.Payload != "" { + msg.Payload, err = base58.Decode(tb.Payload) + if err != nil { + return nil, err + } + } + + if tb.PayloadJson != nil && tb.PayloadJson.Name != "" { + payload, err := json.Marshal(tb.PayloadJson) + + if err != nil { + return nil, err + } else { + msg.Payload = payload + } + } + + msg.GasLimit = tb.GasLimit + if tb.GasPrice != "" { + price, err := ParseUnit(tb.GasPrice) + if err != nil { + return nil, err + } + msg.GasPrice = price.Bytes() + } + if tb.ChainIdHash != "" { + msg.ChainIdHash, err = base58.Decode(tb.ChainIdHash) + if err != nil { + return nil, err + } + } + if tb.Sign != "" { + msg.Sign, err = base58.Decode(tb.Sign) + if err != nil { + return nil, err + } + } + msg.Type = tb.Type + return msg, nil +} + +type InOutPayload struct { + Name string `json:"name,omitempty"` + Arg []interface{} `json:"arg,omitempty"` +} + +type InOutTxBody struct { + Nonce uint64 `json:"nonce,omitempty"` + Account string `json:"account,omitempty"` + Recipient string `json:"recipient,omitempty"` + Amount string `json:"amount,omitempty"` + Payload string `json:"payload,omitempty"` + PayloadJson *types.CallInfo `json:"payloadJson,omitempty"` + GasLimit uint64 `json:"gasLimit,omitempty"` + GasPrice string `json:"gasPrice,omitempty"` + Type types.TxType `json:"type,omitempty"` + ChainIdHash string `json:"chainIdHash,omitempty"` + Sign string `json:"sign,omitempty"` +} + +func (b *InOutTxBody) String() string { + return MarshalJSON(b) +} + +func ConvTxInBlock(msg *types.TxInBlock, payloadType EncodingType) *InOutTxInBlock { + if msg == nil { + return nil + } + tib := &InOutTxInBlock{} + tib.Tx = ConvTx(msg.GetTx(), payloadType) + tib.TxIdx = ConvTxIdx(msg.GetTxIdx()) + return tib +} + +type InOutTxInBlock struct { + TxIdx *InOutTxIdx `json:"txIdx"` + Tx *InOutTx `json:"tx"` +} + +func ConvTxIdx(msg *types.TxIdx) *InOutTxIdx { + if msg == nil { + return nil + } + return &InOutTxIdx{ + BlockHash: base58.Encode(msg.GetBlockHash()), + Idx: msg.GetIdx(), + } +} + +type InOutTxIdx struct { + BlockHash string `json:"blockHash,omitempty"` + Idx int32 `json:"idx,omitempty"` +} + +func (t *InOutTxInBlock) String() string { + return MarshalJSON(t) +} + +func CovPayloadJson(tx *InOutTx) { + if tx.Body.Payload != "" { + payload, err := base58.Decode(tx.Body.Payload) + if err == nil { + tx.Body.PayloadJson = &types.CallInfo{} + _ = json.Unmarshal(payload, tx.Body.PayloadJson) + } + } +} diff --git a/types/jsonrpc/tx_test.go b/types/jsonrpc/tx_test.go new file mode 100644 index 000000000..eea14a575 --- /dev/null +++ b/types/jsonrpc/tx_test.go @@ -0,0 +1,158 @@ +package jsonrpc + +import ( + "testing" + + "github.com/aergoio/aergo/v2/internal/enc/base58" + "github.com/aergoio/aergo/v2/types" + "github.com/stretchr/testify/assert" +) + +func TestParseConvBase58Tx(t *testing.T) { + for _, test := range []struct { + txJson string + }{ + {`[{"Hash":"525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi","Body":{"Nonce":9,"Account":"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd","Recipient":"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR","Amount":"100000000","Payload":null,"Limit":100,"Price":"1","Type":0,"Sign":"3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzLWh1SkkGYMGJ669nCVuYHrhwfg1HrUUp6KDwzK"}}]`}, + {`[{"hash":"525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi","body":{"nonce":9,"account":"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd","recipient":"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR","amount":"100000000","payload":null,"limit":100,"price":"1","type":0,"sign":"3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzLWh1SkkGYMGJ669nCVuYHrhwfg1HrUUp6KDwzK"}}]`}, + } { + res, err := ParseBase58Tx([]byte(test.txJson)) + assert.NoError(t, err, "should be success") + assert.NotEmpty(t, res, "failed to parse json") + assert.Equal(t, "525mQMtsWaDLVJbzQZgTFkSG33gtZsho7m4io1HUCeJi", base58.Encode(res[0].Hash), "wrong hash") + assert.Equal(t, "3tMHYrizQ532D1WJkt5RSs5AcRmq7betw8zvC66Wh3XHUdvNpNzLWh1SkkGYMGJ669nCVuYHrhwfg1HrUUp6KDwzK", base58.Encode(res[0].Body.Sign), "wrong sign") + + account, err := types.DecodeAddress("AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd") + assert.NoError(t, err, "should be success") + assert.Equal(t, account, res[0].Body.Account, "wrong account") + + recipient, err := types.DecodeAddress("AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR") + assert.NoError(t, err, "should be success") + assert.Equal(t, recipient, res[0].Body.Recipient, "wrong recipient") + } +} + +func TestParseBase58TxBody(t *testing.T) { + for _, test := range []struct { + txJson string + }{ + {`{"Nonce":1,"Account":"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd","Recipient":"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR","Amount":"25000","Payload":"aergo","Limit":100,"Price":"1","Type":0,"Sign":"3roWPzztf5aLLh16vAnd2ugcPux3wJ1oqqvqkWARobjuAC32xftF42nnbTkXUQdkDaFvuUmctrpQSv8FAVUKcywHW"}`}, + {`{"nonce":1,"account":"AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd","recipient":"AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR","amount":"25000","payload":"aergo","limit":100,"price":"1","type":0,"sign":"3roWPzztf5aLLh16vAnd2ugcPux3wJ1oqqvqkWARobjuAC32xftF42nnbTkXUQdkDaFvuUmctrpQSv8FAVUKcywHW"}`}, + } { + res, err := ParseBase58TxBody([]byte(test.txJson)) + assert.NoError(t, err, "should be success") + assert.NotEmpty(t, res, "failed to parse json") + + assert.Equal(t, "3roWPzztf5aLLh16vAnd2ugcPux3wJ1oqqvqkWARobjuAC32xftF42nnbTkXUQdkDaFvuUmctrpQSv8FAVUKcywHW", base58.Encode(res.Sign), "wrong sign") + assert.Equal(t, "aergo", base58.Encode(res.Payload), "wrong payload") + account, err := types.DecodeAddress("AsiFCzSukVNUGufJSzSNLA1nKx39NxKcVBEWvW3riyfixcBjN1Qd") + assert.NoError(t, err, "should be success") + assert.Equal(t, account, res.Account, "wrong account") + + recipient, err := types.DecodeAddress("AsjHhFbCuULoUVZPiNNV6WEemtEi7Eiy6G4TDaUsMDiedCARbhQR") + assert.NoError(t, err, "should be success") + assert.Equal(t, recipient, res.Recipient, "wrong recipient") + } +} + +func TestParseTx(t *testing.T) { + testTx := &types.Tx{Body: &types.TxBody{Payload: []byte(`{"Name":"v1createName","Args":["honggildong3"]}`)}} + result := MarshalJSON(ConvTx(testTx, Base58)) + assert.Equal(t, "{\n \"body\": {\n \"payload\": \"22MZAFWvxtVWehpgwEVxrvoqGL5xmcPmyLBiwraDfxRwKUNrV9tmhuB7Uu6ZeJWvp\"\n }\n}", result, "") + result = MarshalJSON(ConvTx(testTx, Raw)) + assert.Equal(t, "{\n \"body\": {\n \"payload\": \"{\\\"Name\\\":\\\"v1createName\\\",\\\"Args\\\":[\\\"honggildong3\\\"]}\"\n }\n}", result, "") +} + +func TestConvTxBody(t *testing.T) { + account, err := types.DecodeAddress(testAccountBase58) + assert.NoError(t, err, "should be success") + recipient, err := types.DecodeAddress(testRecipientBase58) + assert.NoError(t, err, "should be decode recipient") + payload, err := base58.Decode(testPayloadBase58) + assert.NoError(t, err, "should be decode payload") + chainIdHash, err := base58.Decode(testChainIdHashBase58) + assert.NoError(t, err, "should be decode chainIdHash") + sign, err := base58.Decode(testSignBase58) + assert.NoError(t, err, "should be decode sign") + + for _, test := range []struct { + encType EncodingType + types *types.TxBody + inout *InOutTxBody + }{ + {Base58, &types.TxBody{ + Nonce: 1, + Account: account, + Recipient: recipient, + Payload: payload, + GasLimit: 100000, + GasPrice: types.NewAmount(5, types.Aergo).Bytes(), + Type: types.TxType_NORMAL, + ChainIdHash: chainIdHash, + Sign: sign, + }, &InOutTxBody{ + Nonce: 1, + Account: testAccountBase58, + Recipient: testRecipientBase58, + Payload: testPayloadBase58, + GasLimit: 100000, + GasPrice: types.NewAmount(5, types.Aergo).String(), + Type: types.TxType_NORMAL, + ChainIdHash: testChainIdHashBase58, + Sign: testSignBase58, + }}, + {Raw, &types.TxBody{ + Nonce: 1, + Account: account, + Recipient: recipient, + Payload: payload, + GasLimit: 100000, + GasPrice: types.NewAmount(5, types.Aergo).Bytes(), + Type: types.TxType_NORMAL, + ChainIdHash: chainIdHash, + Sign: sign, + }, &InOutTxBody{ + Nonce: 1, + Account: testAccountBase58, + Recipient: testRecipientBase58, + Payload: string(payload), + GasLimit: 100000, + GasPrice: types.NewAmount(5, types.Aergo).String(), + Type: types.TxType_NORMAL, + ChainIdHash: testChainIdHashBase58, + Sign: testSignBase58, + }}, + } { + assert.Equal(t, test.inout, ConvTxBody(test.types, test.encType)) + } +} + +func TestConvTxInBlock(t *testing.T) { + for _, test := range []struct { + types *types.TxInBlock + inout *InOutTxInBlock + }{ + {&types.TxInBlock{}, &InOutTxInBlock{}}, + } { + assert.EqualValues(t, test.inout, ConvTxInBlock(test.types, Base58)) + } +} + +func TestConvTxIdx(t *testing.T) { + blockHash, err := base58.Decode(testBlockHashBase58) + assert.NoError(t, err, "should be decode blockHash") + + for _, test := range []struct { + types *types.TxIdx + inout *InOutTxIdx + }{ + {&types.TxIdx{ + BlockHash: blockHash, + Idx: 1, + }, &InOutTxIdx{ + BlockHash: testBlockHashBase58, + Idx: 1, + }}, + } { + assert.Equal(t, test.inout, ConvTxIdx(test.types)) + } +} diff --git a/cmd/aergocli/util/unit.go b/types/jsonrpc/unit.go similarity index 99% rename from cmd/aergocli/util/unit.go rename to types/jsonrpc/unit.go index 2905ad6b7..d56058f84 100644 --- a/cmd/aergocli/util/unit.go +++ b/types/jsonrpc/unit.go @@ -1,4 +1,4 @@ -package util +package jsonrpc import ( "fmt" diff --git a/cmd/aergocli/util/unit_test.go b/types/jsonrpc/unit_test.go similarity index 99% rename from cmd/aergocli/util/unit_test.go rename to types/jsonrpc/unit_test.go index 42bcb5b99..219c3188e 100644 --- a/cmd/aergocli/util/unit_test.go +++ b/types/jsonrpc/unit_test.go @@ -1,4 +1,4 @@ -package util +package jsonrpc import ( "math/big" diff --git a/types/message/blockchainmsg.go b/types/message/blockchainmsg.go index 5208add9a..df88cd5fc 100644 --- a/types/message/blockchainmsg.go +++ b/types/message/blockchainmsg.go @@ -80,6 +80,19 @@ type GetReceiptRsp struct { Err error } +type GetReceipts struct { + BlockHash []byte +} +type GetReceiptsRsp struct { + Receipts *types.Receipts + Err error +} + +type GetReceiptsByNo struct { + BlockNo types.BlockNo +} +type GetReceiptsByNoRsp GetReceiptsRsp + type GetABI struct { Contract []byte } diff --git a/types/message/web3msg.go b/types/message/web3msg.go new file mode 100644 index 000000000..57c85b343 --- /dev/null +++ b/types/message/web3msg.go @@ -0,0 +1,8 @@ +/* + * @file + * @copyright defined in aergo/LICENSE.txt + */ + +package message + +const Web3Svc = "web3Svc" diff --git a/types/p2p.pb.go b/types/p2p.pb.go index 5992f6f62..8373d61de 100644 --- a/types/p2p.pb.go +++ b/types/p2p.pb.go @@ -40,11 +40,11 @@ const ( // ALREADY_EXISTS ResultStatus_ALREADY_EXISTS ResultStatus = 6 // PERMISSION_DENIED - ResultStatus_PERMISSION_DENIED ResultStatus = 7 - ResultStatus_RESOURCE_EXHAUSTED ResultStatus = 8 + ResultStatus_PERMISSION_DENIED ResultStatus = 7 + ResultStatus_RESOURCE_EXHAUSTED ResultStatus = 8 ResultStatus_FAILED_PRECONDITION ResultStatus = 9 // ABORTED - ResultStatus_ABORTED ResultStatus = 10 + ResultStatus_ABORTED ResultStatus = 10 ResultStatus_OUT_OF_RANGE ResultStatus = 11 // UNIMPLEMENTED indicates operation is not implemented or not // supported/enabled in this service. diff --git a/types/p2ptypes.go b/types/p2ptypes.go index e951285b5..b6cc74ad7 100644 --- a/types/p2ptypes.go +++ b/types/p2ptypes.go @@ -6,6 +6,7 @@ package types import ( + "github.com/mr-tron/base58" "net" "strconv" "strings" @@ -32,10 +33,10 @@ func RandomPeerID() PeerID { } func IDB58Decode(b58 string) (PeerID, error) { - return peer.IDB58Decode(b58) + return peer.Decode(b58) } func IDB58Encode(pid PeerID) string { - return peer.IDB58Encode(pid) + return base58.Encode([]byte(pid)) } func IDFromBytes(b []byte) (PeerID, error) { diff --git a/types/receipt.go b/types/receipt.go index a4757b22b..a351df0fa 100644 --- a/types/receipt.go +++ b/types/receipt.go @@ -485,6 +485,13 @@ func (rs *Receipts) Get() []*Receipt { return rs.receipts } +func (rs *Receipts) GetBlockNo() BlockNo { + if rs == nil { + return 0 + } + return rs.blockNo +} + func (rs *Receipts) Set(receipts []*Receipt) { rs.receipts = receipts } diff --git a/types/rpc.pb.go b/types/rpc.pb.go index d5c43b3b1..555294b2a 100644 --- a/types/rpc.pb.go +++ b/types/rpc.pb.go @@ -2657,6 +2657,138 @@ func (x *EnterpriseConfig) GetValues() []string { return nil } +type ReceiptsParams struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hashornumber []byte `protobuf:"bytes,1,opt,name=hashornumber,proto3" json:"hashornumber,omitempty"` + Paging *PageParams `protobuf:"bytes,2,opt,name=paging,proto3" json:"paging,omitempty"` +} + +func (x *ReceiptsParams) Reset() { + *x = ReceiptsParams{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReceiptsParams) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiptsParams) ProtoMessage() {} + +func (x *ReceiptsParams) ProtoReflect() protoreflect.Message { + mi := &file_rpc_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (*ReceiptsParams) Descriptor() ([]byte, []int) { + return file_rpc_proto_rawDescGZIP(), []int{16} +} + +func (x *ReceiptsParams) GetHashornumber() []byte { + if x != nil { + return x.Hashornumber + } + return nil +} + +func (x *ReceiptsParams) GetPaging() *PageParams { + if x != nil { + return x.Paging + } + return nil +} + +type ReceiptsPaged struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total uint32 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Size uint32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + Receipts []*Receipt `protobuf:"bytes,4,opt,name=body,proto3" json:"receipts,omitempty"` + BlockNo BlockNo `protobuf:"bytes,4,opt,name=body,proto3" json:"blockNo,omitempty"` +} + +func (x *ReceiptsPaged) Reset() { + *x = ReceiptsPaged{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReceiptsPaged) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiptsPaged) ProtoMessage() {} + +func (x *ReceiptsPaged) ProtoReflect() protoreflect.Message { + mi := &file_rpc_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (*ReceiptsPaged) Descriptor() ([]byte, []int) { + return file_rpc_proto_rawDescGZIP(), []int{15} +} + +func (x *ReceiptsPaged) GetTotal() uint32 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ReceiptsPaged) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ReceiptsPaged) GetSize() uint32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *ReceiptsPaged) Get() []*Receipt { + if x != nil { + return x.Receipts + } + return nil +} + +func (x *ReceiptsPaged) GetBlockNo() uint64 { + if x != nil { + return x.BlockNo + } + return 0 +} + var File_rpc_proto protoreflect.FileDescriptor var file_rpc_proto_rawDesc = []byte{ diff --git a/types/state.go b/types/state.go index d0cbe63ac..400e82f2e 100644 --- a/types/state.go +++ b/types/state.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "math/big" - "reflect" "github.com/aergoio/aergo/v2/internal/common" "github.com/aergoio/aergo/v2/internal/enc/base58" @@ -131,15 +130,6 @@ func (id AccountID) String() string { return HashID(id).String() } -// NewState returns an instance of account state -func NewState() *State { - return &State{ - Nonce: 0, - Balance: []byte{0}, - SqlRecoveryPoint: uint64(1), - } -} - // func (st *State) IsEmpty() bool { // return st.Nonce == 0 && st.Balance == 0 // } @@ -151,23 +141,20 @@ func NewState() *State { // return digest.Sum(nil) // } -// func (st *State) Clone() *State { -// if st == nil { -// return nil -// } -// return &State{ -// Nonce: st.Nonce, -// Balance: st.Balance, -// CodeHash: st.CodeHash, -// StorageRoot: st.StorageRoot, -// } -// } - -func Clone(i interface{}) interface{} { - if i == nil { +func (st *State) Clone() *State { + if st == nil { return nil } - return reflect.Indirect(reflect.ValueOf(i)).Interface() + return &State{ + // state: st.state, + sizeCache: st.sizeCache, + unknownFields: st.unknownFields, + Nonce: st.Nonce, + Balance: st.Balance, + CodeHash: st.CodeHash, + StorageRoot: st.StorageRoot, + SqlRecoveryPoint: st.SqlRecoveryPoint, + } } func (st *State) GetBalanceBigInt() *big.Int { diff --git a/types/state_test.go b/types/state_test.go new file mode 100644 index 000000000..f8fa90dd4 --- /dev/null +++ b/types/state_test.go @@ -0,0 +1,44 @@ +package types + +import ( + "testing" + + "github.com/aergoio/aergo/v2/internal/enc/proto" + "github.com/stretchr/testify/require" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +func TestStateClone(t *testing.T) { + for _, test := range []struct { + state *State + }{ + {&State{ + Nonce: 1, + Balance: NewAmount(1, Aergo).Bytes(), + CodeHash: []byte{1}, + StorageRoot: []byte{1}, + SqlRecoveryPoint: 1, + }}, + {&State{ + state: protoimpl.MessageState{}, + sizeCache: 1, + unknownFields: []byte{1, 2, 3}, + + Nonce: 1, + Balance: NewAmount(1, Aergo).Bytes(), + CodeHash: []byte{1}, + StorageRoot: []byte{1}, + SqlRecoveryPoint: 1, + }}, + } { + stateRaw, err := proto.Encode(test.state) + require.NoError(t, err) + + clone := test.state.Clone() + cloneRaw, err := proto.Encode(clone) + require.NoError(t, err) + + require.Equal(t, stateRaw, cloneRaw) + } + +} diff --git a/types/transaction.go b/types/transaction.go index 9609001db..bd4928610 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -372,14 +372,14 @@ func (tx *transaction) Clone() *transaction { } body := &TxBody{ Nonce: tx.GetBody().Nonce, - Account: Clone(tx.GetBody().Account).([]byte), - Recipient: Clone(tx.GetBody().Recipient).([]byte), - Amount: Clone(tx.GetBody().Amount).([]byte), - Payload: Clone(tx.GetBody().Payload).([]byte), + Account: tx.GetBody().Account, + Recipient: tx.GetBody().Recipient, + Amount: tx.GetBody().Amount, + Payload: tx.GetBody().Payload, GasLimit: tx.GetBody().GasLimit, - GasPrice: Clone(tx.GetBody().GasPrice).([]byte), + GasPrice: tx.GetBody().GasPrice, Type: tx.GetBody().Type, - Sign: Clone(tx.GetBody().Sign).([]byte), + Sign: tx.GetBody().Sign, } res := &transaction{ Tx: &Tx{Body: body},