refactor incoming receipts handling; store both receipts+proof in block; add CXReceiptsProof verification

pull/1368/head
chao 5 years ago committed by Chao Ma
parent 7b9db27e99
commit 2913ff7157
  1. 14
      api/proto/node/node.go
  2. 2
      consensus/consensus_service.go
  3. 2
      consensus/engine/consensus_engine.go
  4. 1
      core/state_processor.go
  5. 15
      core/state_transition.go
  6. 10
      core/types/block.go
  7. 112
      core/types/cx_receipt.go
  8. 5
      core/types/transaction_signing.go
  9. 6
      node/node.go
  10. 83
      node/node_cross_shard.go
  11. 8
      node/node_handler.go
  12. 18
      node/node_newblock.go
  13. 20
      node/worker/worker.go

@ -35,12 +35,6 @@ type BlockchainSyncMessage struct {
BlockHashes []common.Hash
}
// CXReceiptsMessage carrys the cross shard receipts and merkle proof
type CXReceiptsMessage struct {
Receipts types.CXReceipts
MerkleProof *types.CXMerkleProof
}
// BlockchainSyncMessageType represents BlockchainSyncMessageType type.
type BlockchainSyncMessageType int
@ -206,9 +200,9 @@ func DeserializeEpochShardStateFromMessage(payload []byte) (*types.EpochShardSta
return epochShardState, nil
}
// ConstructCXReceiptsMessage constructs cross shard receipts and merkle proof
func ConstructCXReceiptsMessage(cxs types.CXReceipts, mkp *types.CXMerkleProof) []byte {
msg := &CXReceiptsMessage{Receipts: cxs, MerkleProof: mkp}
// ConstructCXReceiptsProof constructs cross shard receipts and merkle proof
func ConstructCXReceiptsProof(cxs types.CXReceipts, mkp *types.CXMerkleProof) []byte {
msg := &types.CXReceiptsProof{Receipts: cxs, MerkleProof: mkp}
byteBuffer := bytes.NewBuffer([]byte{byte(proto.Node)})
byteBuffer.WriteByte(byte(Block))
@ -216,7 +210,7 @@ func ConstructCXReceiptsMessage(cxs types.CXReceipts, mkp *types.CXMerkleProof)
by, err := rlp.EncodeToBytes(msg)
if err != nil {
utils.Logger().Error().Err(err).Msg("[ConstructCXReceiptsMessage] Encode CXReceiptsMessage Error")
utils.Logger().Error().Err(err).Msg("[ConstructCXReceiptsProof] Encode CXReceiptsProof Error")
return []byte{}
}
byteBuffer.Write(by)

@ -284,7 +284,7 @@ func (consensus *Consensus) VerifySeal(chain consensus_engine.ChainReader, heade
// Finalize implements consensus.Engine, accumulating the block rewards,
// setting the final state and assembling the block.
func (consensus *Consensus) Finalize(chain consensus_engine.ChainReader, header *types.Header, state *state.DB, txs []*types.Transaction, receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceipt) (*types.Block, error) {
func (consensus *Consensus) Finalize(chain consensus_engine.ChainReader, header *types.Header, state *state.DB, txs []*types.Transaction, receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceiptsProof) (*types.Block, error) {
// Accumulate any block and uncle rewards and commit the final state root
// Header seems complete, assemble into a block and return
if err := accumulateRewards(chain, state, header); err != nil {

@ -67,7 +67,7 @@ type Engine interface {
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
Finalize(chain ChainReader, header *types.Header, state *state.DB, txs []*types.Transaction,
receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceipt) (*types.Block, error)
receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceiptsProof) (*types.Block, error)
// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.

@ -27,6 +27,7 @@ import (
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils"
)
// StateProcessor is a basic Processor, which takes care of transitioning

@ -136,11 +136,20 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool,
}
// ApplyIncomingReceipt will add amount into ToAddress in the receipt
func ApplyIncomingReceipt(db *state.DB, cx *types.CXReceipt) {
if cx == nil || cx.To == nil {
func ApplyIncomingReceipt(db *state.DB, cxp *types.CXReceiptsProof) {
if cxp == nil {
return
}
db.AddBalance(*cx.To, cx.Amount)
// TODO: how to charge gas here?
for _, cx := range cxp.Receipts {
utils.GetLogInstance().Debug("hehe add incoming receipts")
if cx == nil || cx.To == nil { // should not happend
utils.Logger().Warn().Msg("ApplyIncomingReceipts: Invalid incoming receipt!!")
continue
}
db.AddBalance(*cx.To, cx.Amount)
}
}
// to returns the recipient of the message.

@ -163,7 +163,7 @@ type Block struct {
header *Header
uncles []*Header
transactions Transactions
incomingReceipts CXReceipts
incomingReceipts CXReceiptsProofs
// caches
hash atomic.Value
@ -228,7 +228,7 @@ type storageblock struct {
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs,
// and receipts.
func NewBlock(header *Header, txs []*Transaction, receipts []*Receipt, outcxs []*CXReceipt, incxs []*CXReceipt) *Block {
func NewBlock(header *Header, txs []*Transaction, receipts []*Receipt, outcxs []*CXReceipt, incxs []*CXReceiptsProof) *Block {
b := &Block{header: CopyHeader(header)}
// TODO: panic if len(txs) != len(receipts)
@ -252,8 +252,8 @@ func NewBlock(header *Header, txs []*Transaction, receipts []*Receipt, outcxs []
if len(incxs) == 0 {
b.header.IncomingReceiptHash = EmptyRootHash
} else {
b.header.IncomingReceiptHash = DeriveSha(CXReceipts(incxs))
b.incomingReceipts = make(CXReceipts, len(incxs))
b.header.IncomingReceiptHash = DeriveSha(CXReceiptsProofs(incxs))
b.incomingReceipts = make(CXReceiptsProofs, len(incxs))
copy(b.incomingReceipts, incxs)
}
@ -350,7 +350,7 @@ func (b *Block) Transactions() Transactions {
}
// IncomingReceipts returns verified outgoing receipts
func (b *Block) IncomingReceipts() CXReceipts {
func (b *Block) IncomingReceipts() CXReceiptsProofs {
return b.incomingReceipts
}

@ -1,10 +1,15 @@
package types
import (
"bytes"
"encoding/binary"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/harmony/internal/ctxerror"
)
// CXReceipt represents a receipt for cross-shard transaction
@ -72,18 +77,105 @@ type CXMerkleProof struct {
CXShardHashes []common.Hash // ordered hash list, each hash corresponds to one destination shard's receipts root hash
}
// CalculateIncomingReceiptsHash calculates the incoming receipts list hash
// the list is already sorted by shardID and then by blockNum before calling this function
// or the list is from the block field which is already sorted
func CalculateIncomingReceiptsHash(receiptsList []CXReceipts) common.Hash {
if len(receiptsList) == 0 {
return EmptyRootHash
// CXReceiptsProof carrys the cross shard receipts and merkle proof
type CXReceiptsProof struct {
Receipts CXReceipts
MerkleProof *CXMerkleProof
}
// CXReceiptsProofs is a list of CXReceiptsProof
type CXReceiptsProofs []*CXReceiptsProof
// Len returns the length of s.
func (cs CXReceiptsProofs) Len() int { return len(cs) }
// Swap swaps the i'th and the j'th element in s.
func (cs CXReceiptsProofs) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
// GetRlp implements Rlpable and returns the i'th element of s in rlp.
func (cs CXReceiptsProofs) GetRlp(i int) []byte {
if len(cs) == 0 {
return []byte{}
}
enc, _ := rlp.EncodeToBytes(cs[i])
return enc
}
// ToShardID returns the destination shardID of the cxReceipt
// Not used
func (cs CXReceiptsProofs) ToShardID(i int) uint32 {
return 0
}
// MaxToShardID returns the maximum destination shardID of cxReceipts
// Not used
func (cs CXReceiptsProofs) MaxToShardID() uint32 {
return 0
}
// GetToShardID get the destination shardID, return error if there is more than one unique shardID
func (cxp *CXReceiptsProof) GetToShardID() (uint32, error) {
var shardID uint32
if cxp == nil || len(cxp.Receipts) == 0 {
return uint32(0), ctxerror.New("[GetShardID] CXReceiptsProof or its receipts is NIL")
}
for i, cx := range cxp.Receipts {
if i == 0 {
shardID = cx.ToShardID
} else if shardID == cx.ToShardID {
continue
} else {
return shardID, ctxerror.New("[GetShardID] CXReceiptsProof contains distinct ToShardID")
}
}
return shardID, nil
}
// IsValidCXReceiptsProof checks whether the given CXReceiptsProof is consistency with itself
// Remaining to check whether there is a corresonding block finalized
func (cxp *CXReceiptsProof) IsValidCXReceiptsProof() error {
toShardID, err := cxp.GetToShardID()
if err != nil {
return ctxerror.New("[IsValidCXReceiptsProof] invalid shardID").WithCause(err)
}
merkleProof := cxp.MerkleProof
shardRoot := common.Hash{}
foundMatchingShardID := false
byteBuffer := bytes.NewBuffer([]byte{})
// prepare to calculate source shard outgoing cxreceipts root hash
for j := 0; j < len(merkleProof.ShardIDs); j++ {
sKey := make([]byte, 4)
binary.BigEndian.PutUint32(sKey, merkleProof.ShardIDs[j])
byteBuffer.Write(sKey)
byteBuffer.Write(merkleProof.CXShardHashes[j][:])
if merkleProof.ShardIDs[j] == toShardID {
shardRoot = merkleProof.CXShardHashes[j]
foundMatchingShardID = true
}
}
if !foundMatchingShardID {
return ctxerror.New("[IsValidCXReceiptsProof] Didn't find matching shardID")
}
sourceShardID := merkleProof.ShardID
sourceBlockNum := merkleProof.BlockNum
sourceOutgoingCXReceiptsHash := merkleProof.CXReceiptHash
sha := DeriveSha(cxp.Receipts)
// (1) verify the CXReceipts trie root match
if sha != shardRoot {
return ctxerror.New("[IsValidCXReceiptsProof] Trie Root of ReadCXReceipts Not Match", "sourceShardID", sourceShardID, "sourceBlockNum", sourceBlockNum, "calculated", sha, "got", shardRoot)
}
incomingReceipts := CXReceipts{}
for _, receipts := range receiptsList {
incomingReceipts = append(incomingReceipts, receipts...)
// (2) verify the outgoingCXReceiptsHash match
outgoingHashFromSourceShard := crypto.Keccak256Hash(byteBuffer.Bytes())
if outgoingHashFromSourceShard != sourceOutgoingCXReceiptsHash {
return ctxerror.New("[ProcessReceiptMessage] IncomingReceiptRootHash from source shard not match", "sourceShardID", sourceShardID, "sourceBlockNum", sourceBlockNum, "calculated", outgoingHashFromSourceShard, "got", sourceOutgoingCXReceiptsHash)
}
return DeriveSha(incomingReceipts)
return nil
}

@ -232,11 +232,14 @@ func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) {
}
func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {
V := byte(Vb.Uint64() - 27)
fmt.Println("ops0", "R", R, "S", S, "V", V)
if Vb.BitLen() > 8 {
fmt.Println("ops1", "R", R, "S", S, "V", V)
return common.Address{}, ErrInvalidSig
}
V := byte(Vb.Uint64() - 27)
if !crypto.ValidateSignatureValues(V, R, S, homestead) {
fmt.Println("ops2", "R", R, "S", S, "V", V)
return common.Address{}, ErrInvalidSig
}
// encode the signature in uncompressed format

@ -6,8 +6,6 @@ import (
"sync"
"time"
"github.com/harmony-one/harmony/api/proto/node"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/accounts"
"github.com/harmony-one/harmony/api/client"
@ -94,7 +92,7 @@ type Node struct {
pendingCrossLinks []*types.Header
pendingClMutex sync.Mutex
pendingCXReceipts []*node.CXReceiptsMessage // All the receipts received but not yet processed for Consensus
pendingCXReceipts []*types.CXReceiptsProof // All the receipts received but not yet processed for Consensus
pendingCXMutex sync.Mutex
// Shard databases
@ -256,7 +254,7 @@ func (node *Node) AddPendingTransaction(newTx *types.Transaction) {
}
// AddPendingReceipts adds one receipt message to pending list.
func (node *Node) AddPendingReceipts(receipts *node.CXReceiptsMessage) {
func (node *Node) AddPendingReceipts(receipts *types.CXReceiptsProof) {
if node.NodeConfig.GetNetworkType() != nodeconfig.Mainnet {
node.pendingCXMutex.Lock()
node.pendingCXReceipts = append(node.pendingCXReceipts, receipts)

@ -5,17 +5,10 @@ import (
"errors"
"math/big"
"github.com/harmony-one/harmony/core"
"bytes"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
proto_node "github.com/harmony-one/harmony/api/proto/node"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
bls_cosi "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/ctxerror"
@ -92,6 +85,17 @@ func (node *Node) ProcessHeaderMessage(msgPayload []byte) {
}
}
func (node *Node) verifyIncomingReceipts(block *types.Block) error {
cxps := block.IncomingReceipts()
for _, cxp := range cxps {
if err := cxp.IsValidCXReceiptsProof(); err != nil {
return ctxerror.New("[verifyIncomingReceipts] verification failed").WithCause(err)
}
}
// TODO: add crosslink blockHeaderHash checking
return nil
}
// VerifyCrosslinkHeader verifies the header is valid against the prevHeader.
func (node *Node) VerifyCrosslinkHeader(prevHeader, header *types.Header) error {
@ -207,68 +211,23 @@ func (node *Node) ProposeCrossLinkDataForBeaconchain() (types.CrossLinks, error)
// ProcessReceiptMessage store the receipts and merkle proof in local data store
func (node *Node) ProcessReceiptMessage(msgPayload []byte) {
cxmsg := proto_node.CXReceiptsMessage{}
if err := rlp.DecodeBytes(msgPayload, &cxmsg); err != nil {
cxp := types.CXReceiptsProof{}
if err := rlp.DecodeBytes(msgPayload, &cxp); err != nil {
utils.Logger().Error().Err(err).Msg("[ProcessReceiptMessage] Unable to Decode message Payload")
return
}
merkleProof := cxmsg.MerkleProof
myShardRoot := common.Hash{}
var foundMyShard bool
byteBuffer := bytes.NewBuffer([]byte{})
if len(merkleProof.ShardIDs) == 0 {
utils.Logger().Warn().Msg("[ProcessReceiptMessage] There is No non-empty destination shards")
if err := cxp.IsValidCXReceiptsProof(); err != nil {
utils.Logger().Error().Err(err).Msg("[ProcessReceiptMessage] Invalid CXReceiptsProof")
return
}
// Find receipts with my shard as destination
// and prepare to calculate source shard outgoing cxreceipts root hash
for j := 0; j < len(merkleProof.ShardIDs); j++ {
sKey := make([]byte, 4)
binary.BigEndian.PutUint32(sKey, merkleProof.ShardIDs[j])
byteBuffer.Write(sKey)
byteBuffer.Write(merkleProof.CXShardHashes[j][:])
if merkleProof.ShardIDs[j] == node.Consensus.ShardID {
foundMyShard = true
myShardRoot = merkleProof.CXShardHashes[j]
}
}
if !foundMyShard {
utils.Logger().Warn().Msg("[ProcessReceiptMessage] Not Found My Shard in CXReceipt Message")
return
}
// Check whether the receipts matches the receipt merkle root
receiptsForMyShard := cxmsg.Receipts
if len(receiptsForMyShard) == 0 {
return
}
sourceShardID := merkleProof.ShardID
sourceBlockNum := merkleProof.BlockNum
sourceBlockHash := merkleProof.BlockHash
sourceOutgoingCXReceiptsHash := merkleProof.CXReceiptHash
sha := types.DeriveSha(receiptsForMyShard)
// (1) verify the CXReceipts trie root match
if sha != myShardRoot {
utils.Logger().Warn().Uint32("sourceShardID", sourceShardID).Interface("sourceBlockNum", sourceBlockNum).Interface("calculated", sha).Interface("got", myShardRoot).Msg("[ProcessReceiptMessage] Trie Root of ReadCXReceipts Not Match")
return
}
// (2) verify the outgoingCXReceiptsHash match
outgoingHashFromSourceShard := crypto.Keccak256Hash(byteBuffer.Bytes())
if outgoingHashFromSourceShard != sourceOutgoingCXReceiptsHash {
utils.Logger().Warn().Uint32("sourceShardID", sourceShardID).Interface("sourceBlockNum", sourceBlockNum).Interface("calculated", outgoingHashFromSourceShard).Interface("got", sourceOutgoingCXReceiptsHash).Msg("[ProcessReceiptMessage] IncomingReceiptRootHash from source shard not match")
return
}
// TODO: check message signature is from the nodes of source shard.
// TODO: (3) check message signature is from the nodes of source shard.
node.Blockchain().WriteCXReceipts(sourceShardID, sourceBlockNum.Uint64(), sourceBlockHash, receiptsForMyShard, true)
// TODO: remove it in future if not useful
node.Blockchain().WriteCXReceipts(cxp.MerkleProof.ShardID, cxp.MerkleProof.BlockNum.Uint64(), cxp.MerkleProof.BlockHash, cxp.Receipts, true)
node.AddPendingReceipts(&cxmsg)
node.AddPendingReceipts(&cxp)
}
// ProcessCrossShardTx verify and process cross shard transaction on destination shard

@ -331,7 +331,7 @@ func (node *Node) BroadcastCXReceipts(newBlock *types.Block) {
utils.Logger().Info().Uint32("ToShardID", uint32(i)).Msg("[BroadcastCXReceipts] ReadCXReceipts and MerkleProof Found")
groupID := p2p.ShardID(i)
go node.host.SendMessageToGroups([]p2p.GroupID{p2p.NewGroupIDByShardID(groupID)}, host.ConstructP2pMessage(byte(0), proto_node.ConstructCXReceiptsMessage(cxReceipts, merkleProof)))
go node.host.SendMessageToGroups([]p2p.GroupID{p2p.NewGroupIDByShardID(groupID)}, host.ConstructP2pMessage(byte(0), proto_node.ConstructCXReceiptsProof(cxReceipts, merkleProof)))
}
}
@ -367,6 +367,12 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
}
}
err = node.verifyIncomingReceipts(newBlock)
if err != nil {
return ctxerror.New("[VerifyNewBlock] Cannot ValidateNewBlock", "blockHash", newBlock.Hash(),
"numIncomingReceipts", len(newBlock.IncomingReceipts())).WithCause(err)
}
// TODO: verify the vrf randomness
// _ = newBlock.Header().Vrf

@ -89,7 +89,7 @@ func (node *Node) WaitForConsensusReadyv2(readySignal chan struct{}, stopChan ch
}
// Propose cross shard receipts
receiptsList := node.proposeReceipts()
receiptsList := node.proposeReceiptsProof()
if len(receiptsList) != 0 {
if err := node.Worker.CommitReceipts(receiptsList, coinbase); err != nil {
ctxerror.Log15(utils.GetLogger().Error,
@ -210,28 +210,28 @@ func (node *Node) proposeLocalShardState(block *types.Block) {
}
}
func (node *Node) proposeReceipts() []types.CXReceipts {
receiptsList := []types.CXReceipts{}
func (node *Node) proposeReceiptsProof() []*types.CXReceiptsProof {
receiptsList := []*types.CXReceiptsProof{}
node.pendingCXMutex.Lock()
sort.Slice(node.pendingCXReceipts, func(i, j int) bool {
return node.pendingCXReceipts[i].MerkleProof.ShardID < node.pendingCXReceipts[j].MerkleProof.ShardID || (node.pendingCXReceipts[i].MerkleProof.ShardID == node.pendingCXReceipts[j].MerkleProof.ShardID && node.pendingCXReceipts[i].MerkleProof.BlockNum.Cmp(node.pendingCXReceipts[j].MerkleProof.BlockNum) < 0)
})
for _, receiptMsg := range node.pendingCXReceipts {
// sourceShardID := receiptMsg.MerkleProof.ShardID
// sourceBlockNum := receiptMsg.MerkleProof.BlockNum
for _, cxp := range node.pendingCXReceipts {
// sourceShardID := cxp.MerkleProof.ShardID
// sourceBlockNum := cxp.MerkleProof.BlockNum
//
// beaconChain := node.Blockchain() // TODO: read from real beacon chain
// crossLink, err := beaconChain.ReadCrossLink(sourceShardID, sourceBlockNum.Uint64(), false)
// if err == nil {
// // verify the source block hash is from a finalized block
// if crossLink.ChainHeader.Hash() == receiptMsg.MerkleProof.BlockHash && crossLink.ChainHeader.OutgoingReceiptHash == receiptMsg.MerkleProof.CXReceiptHash {
// receiptsList = append(receiptsList, receiptMsg.Receipts)
// if crossLink.ChainHeader.Hash() == cxp.MerkleProof.BlockHash && crossLink.ChainHeader.OutgoingReceiptHash == cxp.MerkleProof.CXReceiptHash {
// receiptsList = append(receiptsList, cxp.Receipts)
// }
// }
// TODO: remove it after beacon chain sync is ready, for pass the test only
receiptsList = append(receiptsList, receiptMsg.Receipts)
receiptsList = append(receiptsList, cxp)
}
node.pendingCXMutex.Unlock()
return receiptsList

@ -25,8 +25,8 @@ type environment struct {
header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
outcxs []*types.CXReceipt // cross shard transaction receipts (source shard)
incxs []*types.CXReceipt // cross shard receipts (desitinatin shard)
outcxs []*types.CXReceipt // cross shard transaction receipts (source shard)
incxs []*types.CXReceiptsProof // cross shard receipts and its proof (desitinatin shard)
}
// Worker is the main object which takes care of submitting new work to consensus engine
@ -113,13 +113,19 @@ func (w *Worker) CommitTransactions(txs types.Transactions, coinbase common.Addr
}
// CommitReceipts commits a list of already verified incoming cross shard receipts
func (w *Worker) CommitReceipts(receiptsList []types.CXReceipts, coinbase common.Address) error {
func (w *Worker) CommitReceipts(receiptsList []*types.CXReceiptsProof, coinbase common.Address) error {
if w.current.gasPool == nil {
w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
}
w.current.header.IncomingReceiptHash = types.CalculateIncomingReceiptsHash(receiptsList)
for _, receipts := range receiptsList {
w.current.incxs = append(w.current.incxs, receipts...)
if len(receiptsList) == 0 {
w.current.header.IncomingReceiptHash = types.EmptyRootHash
} else {
w.current.header.IncomingReceiptHash = types.DeriveSha(types.CXReceiptsProofs(receiptsList))
}
for _, cx := range receiptsList {
w.current.incxs = append(w.current.incxs, cx)
}
return nil
}
@ -180,7 +186,7 @@ func (w *Worker) OutgoingReceipts() []*types.CXReceipt {
}
// IncomingReceipts get incoming receipts in destination shard that is received from source shard
func (w *Worker) IncomingReceipts() []*types.CXReceipt {
func (w *Worker) IncomingReceipts() []*types.CXReceiptsProof {
return w.current.incxs
}

Loading…
Cancel
Save