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 }