You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
14 KiB
431 lines
14 KiB
8 months ago
|
package wiki
|
||
4 years ago
|
|
||
|
import (
|
||
|
"context"
|
||
4 years ago
|
"fmt"
|
||
4 years ago
|
"math/big"
|
||
|
|
||
8 months ago
|
v3 "github.com/woop-chain/woop/block/v3"
|
||
3 years ago
|
|
||
4 years ago
|
"github.com/ethereum/go-ethereum/common"
|
||
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||
|
"github.com/ethereum/go-ethereum/event"
|
||
8 months ago
|
"github.com/woop-chain/woop/block"
|
||
|
"github.com/woop-chain/woop/core"
|
||
|
"github.com/woop-chain/woop/core/rawdb"
|
||
|
"github.com/woop-chain/woop/core/state"
|
||
|
"github.com/woop-chain/woop/core/types"
|
||
|
"github.com/woop-chain/woop/crypto/bls"
|
||
|
internal_bls "github.com/woop-chain/woop/crypto/bls"
|
||
|
"github.com/woop-chain/woop/eth/rpc"
|
||
|
internal_common "github.com/woop-chain/woop/internal/common"
|
||
|
"github.com/woop-chain/woop/internal/params"
|
||
|
"github.com/woop-chain/woop/internal/utils"
|
||
|
"github.com/woop-chain/woop/shard"
|
||
|
"github.com/woop-chain/woop/staking/availability"
|
||
|
stakingReward "github.com/woop-chain/woop/staking/reward"
|
||
4 years ago
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// ChainConfig ...
|
||
8 months ago
|
func (wiki *Woop) ChainConfig() *params.ChainConfig {
|
||
|
return wiki.BlockChain.Config()
|
||
4 years ago
|
}
|
||
|
|
||
|
// GetShardState ...
|
||
8 months ago
|
func (wiki *Woop) GetShardState() (*shard.State, error) {
|
||
|
return wiki.BlockChain.ReadShardState(wiki.BlockChain.CurrentHeader().Epoch())
|
||
4 years ago
|
}
|
||
|
|
||
|
// GetBlockSigners ..
|
||
8 months ago
|
func (wiki *Woop) GetBlockSigners(
|
||
4 years ago
|
ctx context.Context, blockNum rpc.BlockNumber,
|
||
4 years ago
|
) (shard.SlotList, *internal_bls.Mask, error) {
|
||
8 months ago
|
blk, err := wiki.BlockByNumber(ctx, blockNum)
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
8 months ago
|
blockWithSigners, err := wiki.BlockByNumber(ctx, blockNum+1)
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
4 years ago
|
if blockWithSigners == nil {
|
||
|
return nil, nil, fmt.Errorf("block number %v not found", blockNum+1)
|
||
|
}
|
||
8 months ago
|
committee, err := wiki.GetValidators(blk.Epoch())
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
pubKeys := make([]internal_bls.PublicKeyWrapper, len(committee.Slots))
|
||
4 years ago
|
for i, validator := range committee.Slots {
|
||
|
key, err := bls.BytesToBLSPublicKey(validator.BLSPublicKey[:])
|
||
|
if err != nil {
|
||
4 years ago
|
return nil, nil, err
|
||
|
}
|
||
4 years ago
|
pubKeys[i] = internal_bls.PublicKeyWrapper{
|
||
|
Bytes: validator.BLSPublicKey,
|
||
|
Object: key,
|
||
|
}
|
||
4 years ago
|
}
|
||
1 year ago
|
mask := internal_bls.NewMask(pubKeys)
|
||
4 years ago
|
err = mask.SetMask(blockWithSigners.Header().LastCommitBitmap())
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return committee.Slots, mask, nil
|
||
|
}
|
||
|
|
||
4 years ago
|
// DetailedBlockSignerInfo contains all of the block singing information
|
||
|
type DetailedBlockSignerInfo struct {
|
||
4 years ago
|
// Signers are all the signers for the block
|
||
|
Signers shard.SlotList
|
||
4 years ago
|
// Committee when the block was signed.
|
||
|
Committee shard.SlotList
|
||
|
BlockHash common.Hash
|
||
|
}
|
||
|
|
||
|
// GetDetailedBlockSignerInfo fetches the block signer information for any non-genesis block
|
||
8 months ago
|
func (wiki *Woop) GetDetailedBlockSignerInfo(
|
||
4 years ago
|
ctx context.Context, blk *types.Block,
|
||
|
) (*DetailedBlockSignerInfo, error) {
|
||
8 months ago
|
parentBlk, err := wiki.BlockByNumber(ctx, rpc.BlockNumber(blk.NumberU64()-1))
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
8 months ago
|
parentShardState, err := wiki.BlockChain.ReadShardState(parentBlk.Epoch())
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
committee, signers, _, err := availability.BallotResult(
|
||
|
parentBlk.Header(), blk.Header(), parentShardState, blk.ShardID(),
|
||
4 years ago
|
)
|
||
4 years ago
|
return &DetailedBlockSignerInfo{
|
||
|
Signers: signers,
|
||
|
Committee: committee,
|
||
|
BlockHash: blk.Hash(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// PreStakingBlockRewards are the rewards for a block in the pre-staking era (epoch < staking epoch).
|
||
|
type PreStakingBlockRewards map[common.Address]*big.Int
|
||
|
|
||
|
// GetPreStakingBlockRewards for the given block number.
|
||
|
// Calculated rewards are done exactly like chain.AccumulateRewardsAndCountSigs.
|
||
8 months ago
|
func (wiki *Woop) GetPreStakingBlockRewards(
|
||
4 years ago
|
ctx context.Context, blk *types.Block,
|
||
|
) (PreStakingBlockRewards, error) {
|
||
8 months ago
|
if wiki.IsStakingEpoch(blk.Epoch()) {
|
||
4 years ago
|
return nil, fmt.Errorf("block %v is in staking era", blk.Number())
|
||
|
}
|
||
|
|
||
8 months ago
|
if cachedReward, ok := wiki.preStakingBlockRewardsCache.Get(blk.Hash()); ok {
|
||
4 years ago
|
return cachedReward.(PreStakingBlockRewards), nil
|
||
|
}
|
||
|
rewards := PreStakingBlockRewards{}
|
||
|
|
||
8 months ago
|
sigInfo, err := wiki.GetDetailedBlockSignerInfo(ctx, blk)
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
4 years ago
|
last := big.NewInt(0)
|
||
|
count := big.NewInt(int64(len(sigInfo.Signers)))
|
||
|
for i, slot := range sigInfo.Signers {
|
||
|
rewardsForThisAddr, ok := rewards[slot.EcdsaAddress]
|
||
|
if !ok {
|
||
|
rewardsForThisAddr = big.NewInt(0)
|
||
|
}
|
||
|
cur := big.NewInt(0)
|
||
4 years ago
|
cur.Mul(stakingReward.PreStakedBlocks, big.NewInt(int64(i+1))).Div(cur, count)
|
||
4 years ago
|
reward := big.NewInt(0).Sub(cur, last)
|
||
|
rewards[slot.EcdsaAddress] = new(big.Int).Add(reward, rewardsForThisAddr)
|
||
|
last = cur
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
// Report tx fees of the coinbase (== leader)
|
||
8 months ago
|
receipts, err := wiki.GetReceipts(ctx, blk.Hash())
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
txFees := big.NewInt(0)
|
||
|
for _, tx := range blk.Transactions() {
|
||
4 years ago
|
txnHash := tx.HashByType()
|
||
8 months ago
|
dbTx, _, _, receiptIndex := rawdb.ReadTransaction(wiki.ChainDb(), txnHash)
|
||
4 years ago
|
if dbTx == nil {
|
||
4 years ago
|
return nil, fmt.Errorf("could not find receipt for tx: %v", txnHash.String())
|
||
4 years ago
|
}
|
||
4 years ago
|
if len(receipts) <= int(receiptIndex) {
|
||
|
return nil, fmt.Errorf("invalid receipt indext %v (>= num receipts: %v) for tx: %v",
|
||
4 years ago
|
receiptIndex, len(receipts), txnHash.String())
|
||
4 years ago
|
}
|
||
4 years ago
|
txFee := new(big.Int).Mul(tx.GasPrice(), big.NewInt(int64(receipts[receiptIndex].GasUsed)))
|
||
|
txFees = new(big.Int).Add(txFee, txFees)
|
||
4 years ago
|
}
|
||
3 years ago
|
|
||
4 years ago
|
if amt, ok := rewards[blk.Header().Coinbase()]; ok {
|
||
|
rewards[blk.Header().Coinbase()] = new(big.Int).Add(amt, txFees)
|
||
|
} else {
|
||
|
rewards[blk.Header().Coinbase()] = txFees
|
||
|
}
|
||
|
|
||
8 months ago
|
wiki.preStakingBlockRewardsCache.Add(blk.Hash(), rewards)
|
||
4 years ago
|
return rewards, nil
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
// GetLatestChainHeaders ..
|
||
8 months ago
|
func (wiki *Woop) GetLatestChainHeaders() *block.HeaderPair {
|
||
3 years ago
|
pair := &block.HeaderPair{
|
||
|
BeaconHeader: &block.Header{Header: v3.NewHeader()},
|
||
|
ShardHeader: &block.Header{Header: v3.NewHeader()},
|
||
|
}
|
||
|
|
||
8 months ago
|
if wiki.BeaconChain != nil {
|
||
|
pair.BeaconHeader = wiki.BeaconChain.CurrentHeader()
|
||
4 years ago
|
}
|
||
3 years ago
|
|
||
8 months ago
|
if wiki.BlockChain != nil {
|
||
|
pair.ShardHeader = wiki.BlockChain.CurrentHeader()
|
||
3 years ago
|
}
|
||
|
|
||
|
return pair
|
||
4 years ago
|
}
|
||
|
|
||
|
// GetLastCrossLinks ..
|
||
8 months ago
|
func (wiki *Woop) GetLastCrossLinks() ([]*types.CrossLink, error) {
|
||
4 years ago
|
crossLinks := []*types.CrossLink{}
|
||
8 months ago
|
for i := uint32(1); i < shard.Schedule.InstanceForEpoch(wiki.CurrentBlock().Epoch()).NumShards(); i++ {
|
||
|
link, err := wiki.BlockChain.ReadShardLastCrossLink(i)
|
||
4 years ago
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
crossLinks = append(crossLinks, link)
|
||
|
}
|
||
|
|
||
|
return crossLinks, nil
|
||
|
}
|
||
|
|
||
|
// CurrentBlock ...
|
||
8 months ago
|
func (wiki *Woop) CurrentBlock() *types.Block {
|
||
|
return types.NewBlockWithHeader(wiki.BlockChain.CurrentHeader())
|
||
4 years ago
|
}
|
||
|
|
||
2 years ago
|
// CurrentHeader returns the current header from the local chain.
|
||
8 months ago
|
func (wiki *Woop) CurrentHeader() *block.Header {
|
||
|
return wiki.BlockChain.CurrentHeader()
|
||
2 years ago
|
}
|
||
|
|
||
|
// GetBlock returns block by hash.
|
||
8 months ago
|
func (wiki *Woop) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||
|
return wiki.BlockChain.GetBlockByHash(hash), nil
|
||
4 years ago
|
}
|
||
|
|
||
2 years ago
|
// GetHeader returns header by hash.
|
||
8 months ago
|
func (wiki *Woop) GetHeader(ctx context.Context, hash common.Hash) (*block.Header, error) {
|
||
|
return wiki.BlockChain.GetHeaderByHash(hash), nil
|
||
2 years ago
|
}
|
||
|
|
||
4 years ago
|
// GetCurrentBadBlocks ..
|
||
8 months ago
|
func (wiki *Woop) GetCurrentBadBlocks() []core.BadBlock {
|
||
|
return wiki.BlockChain.BadBlocks()
|
||
4 years ago
|
}
|
||
|
|
||
8 months ago
|
func (wiki *Woop) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
|
||
3 years ago
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||
8 months ago
|
return wiki.BlockByNumber(ctx, blockNr)
|
||
3 years ago
|
}
|
||
|
if hash, ok := blockNrOrHash.Hash(); ok {
|
||
8 months ago
|
header := wiki.BlockChain.GetHeaderByHash(hash)
|
||
3 years ago
|
if header == nil {
|
||
|
return nil, errors.New("header for hash not found")
|
||
|
}
|
||
8 months ago
|
if blockNrOrHash.RequireCanonical && wiki.BlockChain.GetCanonicalHash(header.Number().Uint64()) != hash {
|
||
3 years ago
|
return nil, errors.New("hash is not currently canonical")
|
||
|
}
|
||
8 months ago
|
block := wiki.BlockChain.GetBlock(hash, header.Number().Uint64())
|
||
3 years ago
|
if block == nil {
|
||
|
return nil, errors.New("header found, but block body is missing")
|
||
|
}
|
||
|
return block, nil
|
||
|
}
|
||
|
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
||
|
}
|
||
|
|
||
4 years ago
|
// GetBalance returns balance of an given address.
|
||
8 months ago
|
func (wiki *Woop) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*big.Int, error) {
|
||
|
s, _, err := wiki.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
4 years ago
|
if s == nil || err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return s.GetBalance(address), s.Error()
|
||
|
}
|
||
|
|
||
|
// BlockByNumber ...
|
||
8 months ago
|
func (wiki *Woop) BlockByNumber(ctx context.Context, blockNum rpc.BlockNumber) (*types.Block, error) {
|
||
4 years ago
|
// Pending block is only known by the miner
|
||
|
if blockNum == rpc.PendingBlockNumber {
|
||
|
return nil, errors.New("not implemented")
|
||
|
}
|
||
|
// Otherwise resolve and return the block
|
||
|
if blockNum == rpc.LatestBlockNumber {
|
||
8 months ago
|
return wiki.BlockChain.CurrentBlock(), nil
|
||
4 years ago
|
}
|
||
8 months ago
|
return wiki.BlockChain.GetBlockByNumber(uint64(blockNum)), nil
|
||
4 years ago
|
}
|
||
|
|
||
|
// HeaderByNumber ...
|
||
8 months ago
|
func (wiki *Woop) HeaderByNumber(ctx context.Context, blockNum rpc.BlockNumber) (*block.Header, error) {
|
||
4 years ago
|
// Pending block is only known by the miner
|
||
|
if blockNum == rpc.PendingBlockNumber {
|
||
|
return nil, errors.New("not implemented")
|
||
|
}
|
||
|
// Otherwise resolve and return the block
|
||
|
if blockNum == rpc.LatestBlockNumber {
|
||
8 months ago
|
return wiki.BlockChain.CurrentBlock().Header(), nil
|
||
4 years ago
|
}
|
||
8 months ago
|
return wiki.BlockChain.GetHeaderByNumber(uint64(blockNum)), nil
|
||
4 years ago
|
}
|
||
|
|
||
|
// HeaderByHash ...
|
||
8 months ago
|
func (wiki *Woop) HeaderByHash(ctx context.Context, blockHash common.Hash) (*block.Header, error) {
|
||
|
header := wiki.BlockChain.GetHeaderByHash(blockHash)
|
||
4 years ago
|
if header == nil {
|
||
|
return nil, errors.New("Header is not found")
|
||
|
}
|
||
|
return header, nil
|
||
|
}
|
||
|
|
||
|
// StateAndHeaderByNumber ...
|
||
8 months ago
|
func (wiki *Woop) StateAndHeaderByNumber(ctx context.Context, blockNum rpc.BlockNumber) (*state.DB, *block.Header, error) {
|
||
4 years ago
|
// Pending state is only known by the miner
|
||
|
if blockNum == rpc.PendingBlockNumber {
|
||
|
return nil, nil, errors.New("not implemented")
|
||
|
}
|
||
|
// Otherwise resolve the block number and return its state
|
||
8 months ago
|
header, err := wiki.HeaderByNumber(ctx, blockNum)
|
||
4 years ago
|
if header == nil || err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
8 months ago
|
stateDb, err := wiki.BlockChain.StateAt(header.Root())
|
||
4 years ago
|
return stateDb, header, err
|
||
|
}
|
||
|
|
||
8 months ago
|
func (wiki *Woop) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.DB, *block.Header, error) {
|
||
3 years ago
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||
8 months ago
|
return wiki.StateAndHeaderByNumber(ctx, blockNr)
|
||
3 years ago
|
}
|
||
|
if hash, ok := blockNrOrHash.Hash(); ok {
|
||
8 months ago
|
header, err := wiki.HeaderByHash(ctx, hash)
|
||
3 years ago
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
if header == nil {
|
||
|
return nil, nil, errors.New("header for hash not found")
|
||
|
}
|
||
8 months ago
|
if blockNrOrHash.RequireCanonical && wiki.BlockChain.GetCanonicalHash(header.Number().Uint64()) != hash {
|
||
3 years ago
|
return nil, nil, errors.New("hash is not currently canonical")
|
||
|
}
|
||
8 months ago
|
stateDb, err := wiki.BlockChain.StateAt(header.Root())
|
||
3 years ago
|
return stateDb, header, err
|
||
|
}
|
||
|
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
|
||
|
}
|
||
|
|
||
4 years ago
|
// GetLeaderAddress returns the one address of the leader, given the coinbaseAddr.
|
||
|
// Note that the coinbaseAddr is overloaded with the BLS pub key hash in staking era.
|
||
8 months ago
|
func (wiki *Woop) GetLeaderAddress(coinbaseAddr common.Address, epoch *big.Int) string {
|
||
|
if wiki.IsStakingEpoch(epoch) {
|
||
|
if leader, exists := wiki.leaderCache.Get(coinbaseAddr); exists {
|
||
4 years ago
|
bech32, _ := internal_common.AddressToBech32(leader.(common.Address))
|
||
|
return bech32
|
||
|
}
|
||
8 months ago
|
committee, err := wiki.GetValidators(epoch)
|
||
4 years ago
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
for _, val := range committee.Slots {
|
||
|
addr := utils.GetAddressFromBLSPubKeyBytes(val.BLSPublicKey[:])
|
||
8 months ago
|
wiki.leaderCache.Add(addr, val.EcdsaAddress)
|
||
4 years ago
|
if addr == coinbaseAddr {
|
||
|
bech32, _ := internal_common.AddressToBech32(val.EcdsaAddress)
|
||
|
return bech32
|
||
|
}
|
||
|
}
|
||
|
return "" // Did not find matching address
|
||
|
}
|
||
|
bech32, _ := internal_common.AddressToBech32(coinbaseAddr)
|
||
|
return bech32
|
||
|
}
|
||
|
|
||
|
// Filter related APIs
|
||
|
|
||
|
// GetLogs ...
|
||
8 months ago
|
func (wiki *Woop) GetLogs(ctx context.Context, blockHash common.Hash, isEth bool) ([][]*types.Log, error) {
|
||
|
receipts := wiki.BlockChain.GetReceiptsByHash(blockHash)
|
||
4 years ago
|
if receipts == nil {
|
||
|
return nil, errors.New("Missing receipts")
|
||
|
}
|
||
4 years ago
|
if isEth {
|
||
8 months ago
|
block := wiki.BlockChain.GetBlockByHash(blockHash)
|
||
4 years ago
|
if block == nil {
|
||
4 years ago
|
return nil, errors.New("Missing block data")
|
||
|
}
|
||
|
txns := block.Transactions()
|
||
2 years ago
|
for i := range receipts {
|
||
4 years ago
|
if i < len(txns) {
|
||
|
ethHash := txns[i].ConvertToEth().Hash()
|
||
|
receipts[i].TxHash = ethHash
|
||
2 years ago
|
for j := range receipts[i].Logs {
|
||
4 years ago
|
// Override log txHash with receipt's
|
||
|
receipts[i].Logs[j].TxHash = ethHash
|
||
|
}
|
||
4 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
4 years ago
|
logs := make([][]*types.Log, len(receipts))
|
||
|
for i, receipt := range receipts {
|
||
|
logs[i] = receipt.Logs
|
||
|
}
|
||
|
return logs, nil
|
||
|
}
|
||
|
|
||
|
// ServiceFilter ...
|
||
8 months ago
|
func (wiki *Woop) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
||
4 years ago
|
// TODO(dm): implement
|
||
|
}
|
||
|
|
||
|
// SubscribeNewTxsEvent subscribes new tx event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||
|
return wiki.TxPool.SubscribeNewTxsEvent(ch)
|
||
4 years ago
|
}
|
||
|
|
||
|
// SubscribeChainEvent subscribes chain event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||
|
return wiki.BlockChain.SubscribeChainEvent(ch)
|
||
4 years ago
|
}
|
||
|
|
||
|
// SubscribeChainHeadEvent subcribes chain head event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
||
|
return wiki.BlockChain.SubscribeChainHeadEvent(ch)
|
||
4 years ago
|
}
|
||
|
|
||
|
// SubscribeChainSideEvent subcribes chain side event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
|
||
|
return wiki.BlockChain.SubscribeChainSideEvent(ch)
|
||
4 years ago
|
}
|
||
|
|
||
|
// SubscribeRemovedLogsEvent subcribes removed logs event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
||
|
return wiki.BlockChain.SubscribeRemovedLogsEvent(ch)
|
||
4 years ago
|
}
|
||
|
|
||
|
// SubscribeLogsEvent subcribes log event.
|
||
8 months ago
|
// TODO: this is not implemented or verified yet for woop.
|
||
|
func (wiki *Woop) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||
|
return wiki.BlockChain.SubscribeLogsEvent(ch)
|
||
4 years ago
|
}
|