From 2d70c0c3a1ed1b2e9c32d3b303229519137721e3 Mon Sep 17 00:00:00 2001 From: Gerui Wang Date: Tue, 21 May 2024 23:06:25 +0800 Subject: [PATCH] GetTransactionAndReceiptProof API web3ext.go --- internal/ethapi/api.go | 43 ++++++++++++ internal/ethapi/trie_proof.go | 39 +++++++++++ internal/ethapi/trie_proof_test.go | 106 +++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 5 ++ 4 files changed, 193 insertions(+) create mode 100644 internal/ethapi/trie_proof.go create mode 100644 internal/ethapi/trie_proof_test.go diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0f75a0bd09..2d03eb4c6e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -532,6 +532,49 @@ func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Add return (*hexutil.Big)(state.GetBalance(address)), state.Error() } +// GetTransactionAndReceiptProof returns the Trie transaction and receipt proof of the given transaction hash. +func (s *PublicBlockChainAPI) GetTransactionAndReceiptProof(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { + tx, blockHash, _, index := core.GetTransaction(s.b.ChainDb(), hash) + if tx == nil { + return nil, nil + } + block, err := s.b.GetBlock(ctx, blockHash) + if err != nil { + return nil, err + } + tx_tr := deriveTrie(block.Transactions()) + + keybuf := new(bytes.Buffer) + rlp.Encode(keybuf, uint(index)) + var tx_proof proofPairList + if err := tx_tr.Prove(keybuf.Bytes(), 0, &tx_proof); err != nil { + return nil, err + } + receipts, err := s.b.GetReceipts(ctx, blockHash) + if err != nil { + return nil, err + } + if len(receipts) <= int(index) { + return nil, nil + } + receipt_tr := deriveTrie(receipts) + var receipt_proof proofPairList + if err := receipt_tr.Prove(keybuf.Bytes(), 0, &receipt_proof); err != nil { + return nil, err + } + fields := map[string]interface{}{ + "blockHash": blockHash, + "txRoot": tx_tr.Hash(), + "receiptRoot": receipt_tr.Hash(), + "key": hexutil.Encode(keybuf.Bytes()), + "txProofKeys": tx_proof.keys, + "txProofValues": tx_proof.values, + "receiptProofKeys": receipt_proof.keys, + "receiptProofValues": receipt_proof.values, + } + return fields, nil +} + // GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { diff --git a/internal/ethapi/trie_proof.go b/internal/ethapi/trie_proof.go new file mode 100644 index 0000000000..4a7747ab8f --- /dev/null +++ b/internal/ethapi/trie_proof.go @@ -0,0 +1,39 @@ +package ethapi + +import ( + "bytes" + + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/rlp" + "github.com/XinFinOrg/XDPoSChain/trie" +) + +// proofPairList implements ethdb.KeyValueWriter and collects the proofs as +// hex-strings of key and value for delivery to rpc-caller. +type proofPairList struct { + keys []string + values []string +} + +func (n *proofPairList) Put(key []byte, value []byte) error { + n.keys = append(n.keys, hexutil.Encode(key)) + n.values = append(n.values, hexutil.Encode(value)) + return nil +} + +func (n *proofPairList) Delete(key []byte) error { + panic("not supported") +} + +// modified from core/types/derive_sha.go +func deriveTrie(list types.DerivableList) *trie.Trie { + keybuf := new(bytes.Buffer) + trie := new(trie.Trie) + for i := 0; i < list.Len(); i++ { + keybuf.Reset() + rlp.Encode(keybuf, uint(i)) + trie.Update(keybuf.Bytes(), list.GetRlp(i)) + } + return trie +} diff --git a/internal/ethapi/trie_proof_test.go b/internal/ethapi/trie_proof_test.go new file mode 100644 index 0000000000..10dd9988db --- /dev/null +++ b/internal/ethapi/trie_proof_test.go @@ -0,0 +1,106 @@ +package ethapi + +import ( + "bytes" + "fmt" + "math/big" + "reflect" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/rlp" + "github.com/XinFinOrg/XDPoSChain/trie" +) + +// implement interface only for testing verifyProof +func (n *proofPairList) Has(key []byte) (bool, error) { + key_hex := hexutil.Encode(key) + for _, k := range n.keys { + if k == key_hex { + return true, nil + } + } + return false, nil +} + +func (n *proofPairList) Get(key []byte) ([]byte, error) { + key_hex := hexutil.Encode(key) + for i, k := range n.keys { + if k == key_hex { + b, err := hexutil.Decode(n.values[i]) + if err != nil { + return nil, err + } + return b, nil + } + } + return nil, fmt.Errorf("key not found") +} + +func TestTransactionProof(t *testing.T) { + to1 := common.HexToAddress("0x01") + to2 := common.HexToAddress("0x02") + t1 := types.NewTransaction(1, to1, big.NewInt(1), 1, big.NewInt(1), []byte{}) + t2 := types.NewTransaction(2, to2, big.NewInt(2), 2, big.NewInt(2), []byte{}) + t3 := types.NewTransaction(3, to2, big.NewInt(3), 3, big.NewInt(3), []byte{}) + t4 := types.NewTransaction(4, to1, big.NewInt(4), 4, big.NewInt(4), []byte{}) + transactions := types.Transactions([]*types.Transaction{t1, t2, t3, t4}) + tr := deriveTrie(transactions) + // for verifying the proof + root := types.DeriveSha(transactions) + for i := 0; i < transactions.Len(); i++ { + var proof proofPairList + keybuf := new(bytes.Buffer) + rlp.Encode(keybuf, uint(i)) + if err := tr.Prove(keybuf.Bytes(), 0, &proof); err != nil { + t.Fatal("Prove err:", err) + } + // verify the proof + value, err := trie.VerifyProof(root, keybuf.Bytes(), &proof) + if err != nil { + t.Fatal("verify proof error") + } + encodedTransaction, err := rlp.EncodeToBytes(transactions[i]) + if err != nil { + t.Fatal("encode receipt error") + } + if !reflect.DeepEqual(encodedTransaction, value) { + t.Fatal("verify does not return the receipt we want") + } + } +} + +func TestReceiptProof(t *testing.T) { + root1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + root2 := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + r1 := types.NewReceipt(root1, false, 1) + r2 := types.NewReceipt(root2, true, 2) + r3 := types.NewReceipt(root2, false, 3) + r4 := types.NewReceipt(root1, true, 4) + receipts := types.Receipts([]*types.Receipt{r1, r2, r3, r4}) + tr := deriveTrie(receipts) + // for verifying the proof + root := types.DeriveSha(receipts) + for i := 0; i < receipts.Len(); i++ { + var proof proofPairList + keybuf := new(bytes.Buffer) + rlp.Encode(keybuf, uint(i)) + if err := tr.Prove(keybuf.Bytes(), 0, &proof); err != nil { + t.Fatal("Prove err:", err) + } + // verify the proof + value, err := trie.VerifyProof(root, keybuf.Bytes(), &proof) + if err != nil { + t.Fatal("verify proof error") + } + encodedReceipt, err := rlp.EncodeToBytes(receipts[i]) + if err != nil { + t.Fatal("encode receipt error") + } + if !reflect.DeepEqual(encodedReceipt, value) { + t.Fatal("verify does not return the receipt we want") + } + } +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 13289a33fb..ae28816ea2 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -502,6 +502,11 @@ web3._extend({ call: 'eth_getRewardByHash', params: 1 }), + new web3._extend.Method({ + name: 'getTransactionAndReceiptProof', + call: 'eth_getTransactionAndReceiptProof', + params: 1 + }), new web3._extend.Method({ name: 'getRawTransactionFromBlock', call: function(args) {