github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/sgd_indexer.go (about)

     1  // Copyright (c) 2023 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package blockindex
     7  
     8  import (
     9  	"context"
    10  	"strings"
    11  
    12  	"github.com/ethereum/go-ethereum/accounts/abi"
    13  	"github.com/ethereum/go-ethereum/common"
    14  	"github.com/iotexproject/iotex-address/address"
    15  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    16  	"github.com/pkg/errors"
    17  	"google.golang.org/protobuf/proto"
    18  
    19  	"github.com/iotexproject/iotex-core/action"
    20  	"github.com/iotexproject/iotex-core/blockchain/block"
    21  	"github.com/iotexproject/iotex-core/blockchain/blockdao"
    22  	"github.com/iotexproject/iotex-core/blockindex/indexpb"
    23  	"github.com/iotexproject/iotex-core/db"
    24  	"github.com/iotexproject/iotex-core/db/batch"
    25  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    26  	"github.com/iotexproject/iotex-core/state"
    27  )
    28  
    29  var (
    30  	_sgdABI abi.ABI
    31  )
    32  
    33  const (
    34  	_sgdContractInterfaceABI = `[
    35  			{
    36  				"anonymous": false,
    37  				"inputs": [
    38  					{
    39  						"indexed": false,
    40  						"internalType": "address",
    41  						"name": "previousAdmin",
    42  						"type": "address"
    43  					},
    44  					{
    45  						"indexed": false,
    46  						"internalType": "address",
    47  						"name": "newAdmin",
    48  						"type": "address"
    49  					}
    50  				],
    51  				"name": "AdminChanged",
    52  				"type": "event"
    53  			},
    54  			{
    55  				"anonymous": false,
    56  				"inputs": [
    57  					{
    58  						"indexed": true,
    59  						"internalType": "address",
    60  						"name": "beacon",
    61  						"type": "address"
    62  					}
    63  				],
    64  				"name": "BeaconUpgraded",
    65  				"type": "event"
    66  			},
    67  			{
    68  				"anonymous": false,
    69  				"inputs": [
    70  					{
    71  						"indexed": false,
    72  						"internalType": "address",
    73  						"name": "contractAddress",
    74  						"type": "address"
    75  					}
    76  				],
    77  				"name": "ContractApproved",
    78  				"type": "event"
    79  			},
    80  			{
    81  				"anonymous": false,
    82  				"inputs": [
    83  					{
    84  						"indexed": false,
    85  						"internalType": "address",
    86  						"name": "contractAddress",
    87  						"type": "address"
    88  					},
    89  					{
    90  						"indexed": false,
    91  						"internalType": "address",
    92  						"name": "recipient",
    93  						"type": "address"
    94  					}
    95  				],
    96  				"name": "ContractRegistered",
    97  				"type": "event"
    98  			},
    99  			{
   100  				"anonymous": false,
   101  				"inputs": [
   102  					{
   103  						"indexed": false,
   104  						"internalType": "address",
   105  						"name": "contractAddress",
   106  						"type": "address"
   107  					}
   108  				],
   109  				"name": "ContractRemoved",
   110  				"type": "event"
   111  			},
   112  			{
   113  				"anonymous": false,
   114  				"inputs": [
   115  					{
   116  						"indexed": false,
   117  						"internalType": "address",
   118  						"name": "contractAddress",
   119  						"type": "address"
   120  					}
   121  				],
   122  				"name": "ContractDisapproved",
   123  				"type": "event"
   124  			},
   125  			{
   126  				"anonymous": false,
   127  				"inputs": [
   128  					{
   129  						"indexed": false,
   130  						"internalType": "uint8",
   131  						"name": "version",
   132  						"type": "uint8"
   133  					}
   134  				],
   135  				"name": "Initialized",
   136  				"type": "event"
   137  			},
   138  			{
   139  				"anonymous": false,
   140  				"inputs": [
   141  					{
   142  						"indexed": true,
   143  						"internalType": "address",
   144  						"name": "previousOwner",
   145  						"type": "address"
   146  					},
   147  					{
   148  						"indexed": true,
   149  						"internalType": "address",
   150  						"name": "newOwner",
   151  						"type": "address"
   152  					}
   153  				],
   154  				"name": "OwnershipTransferred",
   155  				"type": "event"
   156  			},
   157  			{
   158  				"anonymous": false,
   159  				"inputs": [
   160  					{
   161  						"indexed": true,
   162  						"internalType": "address",
   163  						"name": "implementation",
   164  						"type": "address"
   165  					}
   166  				],
   167  				"name": "Upgraded",
   168  				"type": "event"
   169  			}
   170  ]`
   171  )
   172  
   173  func init() {
   174  	var err error
   175  	_sgdABI, err = abi.JSON(strings.NewReader(_sgdContractInterfaceABI))
   176  	if err != nil {
   177  		panic(err)
   178  	}
   179  }
   180  
   181  const (
   182  	_sgdBucket     = "sg"
   183  	_sgdToHeightNS = "hh"
   184  	//TODO (millken): currently we fix the percentage to 30%, we can make it configurable in the future
   185  	_sgdPercentage = uint64(30)
   186  )
   187  
   188  var _sgdCurrentHeight = []byte("currentHeight")
   189  
   190  type (
   191  	// SGDRegistry is the interface for Sharing of Gas-fee with DApps
   192  	SGDRegistry interface {
   193  		blockdao.BlockIndexerWithStart
   194  		// CheckContract returns the contract's eligibility for SGD and percentage
   195  		CheckContract(context.Context, string, uint64) (address.Address, uint64, bool, error)
   196  		// FetchContracts returns all contracts that are eligible for SGD
   197  		FetchContracts(context.Context, uint64) ([]*SGDIndex, error)
   198  	}
   199  
   200  	sgdRegistry struct {
   201  		contract    string
   202  		startHeight uint64
   203  		kvStore     db.KVStore
   204  	}
   205  	// SGDIndex is the struct for SGDIndex
   206  	SGDIndex struct {
   207  		Contract address.Address
   208  		Receiver address.Address
   209  		Approved bool
   210  	}
   211  )
   212  
   213  func sgdIndexFromPb(pb *indexpb.SGDIndex) (*SGDIndex, error) {
   214  	contract, err := address.FromBytes(pb.Contract)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	receiver, err := address.FromBytes(pb.Receiver)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	return &SGDIndex{
   223  		Contract: contract,
   224  		Receiver: receiver,
   225  		Approved: pb.Approved,
   226  	}, nil
   227  }
   228  
   229  func newSgdIndex(contract, receiver []byte) *indexpb.SGDIndex {
   230  	return &indexpb.SGDIndex{
   231  		Contract: contract,
   232  		Receiver: receiver,
   233  	}
   234  }
   235  
   236  // NewSGDRegistry creates a new SGDIndexer
   237  func NewSGDRegistry(contract string, startHeight uint64, kv db.KVStore) SGDRegistry {
   238  	if kv == nil {
   239  		panic("nil kvstore")
   240  	}
   241  	if contract != "" {
   242  		if _, err := address.FromString(contract); err != nil {
   243  			panic("invalid contract address")
   244  		}
   245  	}
   246  	return &sgdRegistry{
   247  		contract:    contract,
   248  		startHeight: startHeight,
   249  		kvStore:     kv,
   250  	}
   251  }
   252  
   253  // Start starts the SGDIndexer
   254  func (sgd *sgdRegistry) Start(ctx context.Context) error {
   255  	err := sgd.kvStore.Start(ctx)
   256  	if err != nil {
   257  		return err
   258  	}
   259  	_, err = sgd.Height()
   260  	if err != nil && errors.Is(err, db.ErrNotExist) {
   261  		return sgd.kvStore.Put(_sgdToHeightNS, _sgdCurrentHeight, byteutil.Uint64ToBytesBigEndian(0))
   262  	}
   263  	return err
   264  }
   265  
   266  // Stop stops the SGDIndexer
   267  func (sgd *sgdRegistry) Stop(ctx context.Context) error {
   268  	return sgd.kvStore.Stop(ctx)
   269  }
   270  
   271  // Height returns the current height of the SGDIndexer
   272  func (sgd *sgdRegistry) Height() (uint64, error) {
   273  	return sgd.height()
   274  }
   275  
   276  // StartHeight returns the start height of the indexer
   277  func (sgd *sgdRegistry) StartHeight() uint64 {
   278  	return sgd.startHeight
   279  }
   280  
   281  // PutBlock puts a block into SGDIndexer
   282  func (sgd *sgdRegistry) PutBlock(ctx context.Context, blk *block.Block) error {
   283  	expectHeight, err := sgd.expectHeight()
   284  	if err != nil {
   285  		return err
   286  	}
   287  	if blk.Height() < expectHeight {
   288  		return nil
   289  	}
   290  	if blk.Height() > expectHeight {
   291  		return errors.Errorf("invalid block height %d, expect %d", blk.Height(), expectHeight)
   292  	}
   293  
   294  	var b = batch.NewBatch()
   295  
   296  	for _, r := range blk.Receipts {
   297  		if r.Status != uint64(iotextypes.ReceiptStatus_Success) {
   298  			continue
   299  		}
   300  		for _, log := range r.Logs() {
   301  			if log.Address != sgd.contract {
   302  				continue
   303  			}
   304  			if err := sgd.handleEvent(b, log); err != nil {
   305  				return err
   306  			}
   307  		}
   308  	}
   309  	b.Put(_sgdToHeightNS, _sgdCurrentHeight, byteutil.Uint64ToBytesBigEndian(blk.Height()), "failed to put current height")
   310  	return sgd.kvStore.WriteBatch(b)
   311  }
   312  
   313  func (sgd *sgdRegistry) handleEvent(b batch.KVStoreBatch, log *action.Log) error {
   314  	abiEvent, err := _sgdABI.EventByID(common.Hash(log.Topics[0]))
   315  	if err != nil {
   316  		return err
   317  	}
   318  	switch abiEvent.Name {
   319  	case "ContractRegistered":
   320  		return sgd.handleContractRegistered(b, log)
   321  	case "ContractApproved":
   322  		return sgd.handleContractApproved(b, log)
   323  	case "ContractDisapproved":
   324  		return sgd.handleContractDisapproved(b, log)
   325  	case "ContractRemoved":
   326  		return sgd.handleContractRemoved(b, log)
   327  	default:
   328  		//skip other events
   329  	}
   330  	return nil
   331  }
   332  
   333  func (sgd *sgdRegistry) handleContractRegistered(b batch.KVStoreBatch, log *action.Log) error {
   334  	var (
   335  		sgdIndex *indexpb.SGDIndex
   336  		event    struct {
   337  			ContractAddress common.Address
   338  			Recipient       common.Address
   339  		}
   340  	)
   341  	if err := _sgdABI.UnpackIntoInterface(&event, "ContractRegistered", log.Data); err != nil {
   342  		return err
   343  	}
   344  
   345  	sgdIndex = newSgdIndex(event.ContractAddress.Bytes(), event.Recipient.Bytes())
   346  	return sgd.putIndex(b, sgdIndex)
   347  }
   348  
   349  func (sgd *sgdRegistry) handleContractApproved(b batch.KVStoreBatch, log *action.Log) error {
   350  	var (
   351  		sgdIndex *indexpb.SGDIndex
   352  		err      error
   353  		event    struct {
   354  			ContractAddress common.Address
   355  		}
   356  	)
   357  	if err := _sgdABI.UnpackIntoInterface(&event, "ContractApproved", log.Data); err != nil {
   358  		return err
   359  	}
   360  
   361  	sgdIndex, err = sgd.getSGDIndex(event.ContractAddress.Bytes())
   362  	if err != nil {
   363  		return err
   364  	}
   365  	if sgdIndex.Approved {
   366  		return errors.New("contract is approved")
   367  	}
   368  	sgdIndex.Approved = true
   369  	return sgd.putIndex(b, sgdIndex)
   370  }
   371  
   372  func (sgd *sgdRegistry) handleContractDisapproved(b batch.KVStoreBatch, log *action.Log) error {
   373  	var (
   374  		sgdIndex *indexpb.SGDIndex
   375  		err      error
   376  		event    struct {
   377  			ContractAddress common.Address
   378  		}
   379  	)
   380  	if err := _sgdABI.UnpackIntoInterface(&event, "ContractDisapproved", log.Data); err != nil {
   381  		return err
   382  	}
   383  
   384  	sgdIndex, err = sgd.getSGDIndex(event.ContractAddress.Bytes())
   385  	if err != nil {
   386  		return err
   387  	}
   388  	if !sgdIndex.Approved {
   389  		return errors.New("contract is not approved")
   390  	}
   391  	sgdIndex.Approved = false
   392  	return sgd.putIndex(b, sgdIndex)
   393  }
   394  
   395  func (sgd *sgdRegistry) handleContractRemoved(b batch.KVStoreBatch, log *action.Log) error {
   396  	var (
   397  		event struct {
   398  			ContractAddress common.Address
   399  		}
   400  	)
   401  	if err := _sgdABI.UnpackIntoInterface(&event, "ContractRemoved", log.Data); err != nil {
   402  		return err
   403  	}
   404  	return sgd.deleteIndex(b, event.ContractAddress.Bytes())
   405  }
   406  
   407  func (sgd *sgdRegistry) putIndex(b batch.KVStoreBatch, sgdIndex *indexpb.SGDIndex) error {
   408  	sgdIndexBytes, err := proto.Marshal(sgdIndex)
   409  	if err != nil {
   410  		return err
   411  	}
   412  	b.Put(_sgdBucket, sgdIndex.Contract, sgdIndexBytes, "failed to put sgd index")
   413  	return nil
   414  }
   415  
   416  func (sgd *sgdRegistry) deleteIndex(b batch.KVStoreBatch, contract []byte) error {
   417  	b.Delete(_sgdBucket, contract, "failed to delete sgd index")
   418  	return nil
   419  }
   420  
   421  // DeleteTipBlock deletes the tip block from SGDIndexer
   422  func (sgd *sgdRegistry) DeleteTipBlock(context.Context, *block.Block) error {
   423  	return errors.New("cannot remove block from indexer")
   424  }
   425  
   426  // CheckContract checks if the contract is a SGD contract
   427  func (sgd *sgdRegistry) CheckContract(ctx context.Context, contract string, height uint64) (address.Address, uint64, bool, error) {
   428  	if err := sgd.validateQueryHeight(height); err != nil {
   429  		return nil, 0, false, err
   430  	}
   431  	addr, err := address.FromString(contract)
   432  	if err != nil {
   433  		return nil, 0, false, err
   434  	}
   435  	sgdIndex, err := sgd.getSGDIndex(addr.Bytes())
   436  	if err != nil {
   437  		// if the contract is not registered, return nil to prevent the evm from throwing error
   438  		if errors.Cause(err) == db.ErrNotExist || errors.Cause(err) == db.ErrBucketNotExist {
   439  			return nil, 0, false, nil
   440  		}
   441  		return nil, 0, false, err
   442  	}
   443  
   444  	addr, err = address.FromBytes(sgdIndex.Receiver)
   445  	return addr, _sgdPercentage, sgdIndex.Approved, err
   446  }
   447  
   448  // getSGDIndex returns the SGDIndex of the contract
   449  func (sgd *sgdRegistry) getSGDIndex(contract []byte) (*indexpb.SGDIndex, error) {
   450  	buf, err := sgd.kvStore.Get(_sgdBucket, contract)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  	sgdIndex := &indexpb.SGDIndex{}
   455  	if err := proto.Unmarshal(buf, sgdIndex); err != nil {
   456  		return nil, err
   457  	}
   458  	return sgdIndex, nil
   459  }
   460  
   461  // FetchContracts returns all contracts that are eligible for SGD
   462  func (sgd *sgdRegistry) FetchContracts(ctx context.Context, height uint64) ([]*SGDIndex, error) {
   463  	if err := sgd.validateQueryHeight(height); err != nil {
   464  		return nil, err
   465  	}
   466  	_, values, err := sgd.kvStore.Filter(_sgdBucket, func(k, v []byte) bool { return true }, nil, nil)
   467  	if err != nil {
   468  		if errors.Cause(err) == db.ErrNotExist || errors.Cause(err) == db.ErrBucketNotExist {
   469  			return nil, errors.Wrapf(state.ErrStateNotExist, "failed to get sgd states of ns = %x", _sgdBucket)
   470  		}
   471  		return nil, err
   472  	}
   473  	sgdIndexes := make([]*SGDIndex, 0, len(values))
   474  	sgdIndexPb := &indexpb.SGDIndex{}
   475  	for _, v := range values {
   476  		if err := proto.Unmarshal(v, sgdIndexPb); err != nil {
   477  			return nil, err
   478  		}
   479  		sgdIndex, err := sgdIndexFromPb(sgdIndexPb)
   480  		if err != nil {
   481  			return nil, err
   482  		}
   483  		sgdIndexes = append(sgdIndexes, sgdIndex)
   484  	}
   485  	return sgdIndexes, nil
   486  }
   487  
   488  func (sgd *sgdRegistry) validateQueryHeight(height uint64) error {
   489  	// 0 means latest height
   490  	if height == 0 {
   491  		return nil
   492  	}
   493  	// Compatible with blocks between feature hard-fork and contract deployed
   494  	if height < sgd.startHeight {
   495  		return nil
   496  	}
   497  	tipHeight, err := sgd.height()
   498  	if err != nil {
   499  		return err
   500  	}
   501  	if height != tipHeight {
   502  		return errors.Errorf("invalid height %d, expect %d", height, tipHeight)
   503  	}
   504  	return nil
   505  }
   506  
   507  func (sgd *sgdRegistry) expectHeight() (uint64, error) {
   508  	tipHeight, err := sgd.height()
   509  	if err != nil {
   510  		return 0, err
   511  	}
   512  	expectHeight := tipHeight + 1
   513  	if expectHeight < sgd.startHeight {
   514  		expectHeight = sgd.startHeight
   515  	}
   516  	return expectHeight, nil
   517  }
   518  
   519  func (sgd *sgdRegistry) height() (uint64, error) {
   520  	h, err := sgd.kvStore.Get(_sgdToHeightNS, _sgdCurrentHeight)
   521  	if err != nil {
   522  		return 0, err
   523  	}
   524  	return byteutil.BytesToUint64BigEndian(h), nil
   525  }