Skip to content

Commit 8c2fe18

Browse files
author
HaoyangLiu
authored
Merge pull request #1 from binance-chain/precompile-contract
R4R: implement precompile contract
2 parents 58cf568 + f7a1cf4 commit 8c2fe18

11 files changed

+1120
-51
lines changed

core/vm/contracts.go

+12-9
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,18 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
6666
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
6767
// contracts used in the Istanbul release.
6868
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
69-
common.BytesToAddress([]byte{1}): &ecrecover{},
70-
common.BytesToAddress([]byte{2}): &sha256hash{},
71-
common.BytesToAddress([]byte{3}): &ripemd160hash{},
72-
common.BytesToAddress([]byte{4}): &dataCopy{},
73-
common.BytesToAddress([]byte{5}): &bigModExp{},
74-
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
75-
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
76-
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
77-
common.BytesToAddress([]byte{9}): &blake2F{},
69+
common.BytesToAddress([]byte{1}): &ecrecover{},
70+
common.BytesToAddress([]byte{2}): &sha256hash{},
71+
common.BytesToAddress([]byte{3}): &ripemd160hash{},
72+
common.BytesToAddress([]byte{4}): &dataCopy{},
73+
common.BytesToAddress([]byte{5}): &bigModExp{},
74+
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
75+
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
76+
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
77+
common.BytesToAddress([]byte{9}): &blake2F{},
78+
79+
common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
80+
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidate{},
7881
}
7982

8083
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.

core/vm/contracts_lightclient.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package vm
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
7+
"github.com/ethereum/go-ethereum/core/vm/lightclient"
8+
"github.com/ethereum/go-ethereum/params"
9+
)
10+
11+
const (
12+
precompileContractInputMetaDataLength uint64 = 32
13+
consensusStateLengthBytesLength uint64 = 32
14+
15+
tmHeaderValidateResultMetaDataLength uint64 = 32
16+
merkleProofValidateResultLength uint64 = 32
17+
)
18+
19+
// input:
20+
// consensus state length | consensus state | tendermint header |
21+
// 32 bytes | | |
22+
func decodeTendermintHeaderValidationInput(input []byte) (*lightclient.ConsensusState, *lightclient.Header, error) {
23+
csLen := binary.BigEndian.Uint64(input[consensusStateLengthBytesLength-8 : consensusStateLengthBytesLength])
24+
if uint64(len(input)) <= consensusStateLengthBytesLength+csLen {
25+
return nil, nil, fmt.Errorf("expected payload size %d, actual size: %d", consensusStateLengthBytesLength+csLen, len(input))
26+
}
27+
28+
cs, err := lightclient.DecodeConsensusState(input[consensusStateLengthBytesLength : consensusStateLengthBytesLength+csLen])
29+
if err != nil {
30+
return nil, nil, err
31+
}
32+
header, err := lightclient.DecodeHeader(input[consensusStateLengthBytesLength+csLen:])
33+
if err != nil {
34+
return nil, nil, err
35+
}
36+
37+
return &cs, header, nil
38+
}
39+
40+
// tmHeaderValidate implemented as a native contract.
41+
type tmHeaderValidate struct{}
42+
43+
func (c *tmHeaderValidate) RequiredGas(input []byte) uint64 {
44+
return params.TendermintHeaderValidateGas
45+
}
46+
47+
func (c *tmHeaderValidate) Run(input []byte) (result []byte, err error) {
48+
defer func() {
49+
if r := recover(); r != nil {
50+
err = fmt.Errorf("internal error: %v\n", r)
51+
}
52+
}()
53+
54+
if uint64(len(input)) <= precompileContractInputMetaDataLength {
55+
return nil, fmt.Errorf("invalid input")
56+
}
57+
58+
payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength])
59+
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength {
60+
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input))
61+
}
62+
63+
cs, header, err := decodeTendermintHeaderValidationInput(input[precompileContractInputMetaDataLength:])
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
validatorSetChanged, err := cs.ApplyHeader(header)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
consensusStateBytes, err := cs.EncodeConsensusState()
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
// result
79+
// | validatorSetChanged | empty | consensusStateBytesLength | new consensusState |
80+
// | 1 byte | 23 bytes | 8 bytes | |
81+
lengthBytes := make([]byte, tmHeaderValidateResultMetaDataLength)
82+
if validatorSetChanged {
83+
copy(lengthBytes[:1], []byte{0x01})
84+
}
85+
consensusStateBytesLength := uint64(len(consensusStateBytes))
86+
binary.BigEndian.PutUint64(lengthBytes[tmHeaderValidateResultMetaDataLength-8:], consensusStateBytesLength)
87+
88+
result = append(lengthBytes, consensusStateBytes...)
89+
90+
return result, nil
91+
}
92+
93+
//------------------------------------------------------------------------------------------------------------------------------------------------
94+
95+
// tmHeaderValidate implemented as a native contract.
96+
type iavlMerkleProofValidate struct{}
97+
98+
func (c *iavlMerkleProofValidate) RequiredGas(input []byte) uint64 {
99+
return params.IAVLMerkleProofValidateGas
100+
}
101+
102+
// input:
103+
// | payload length | payload |
104+
// | 32 bytes | |
105+
func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
106+
defer func() {
107+
if r := recover(); r != nil {
108+
err = fmt.Errorf("internal error: %v\n", r)
109+
}
110+
}()
111+
112+
if uint64(len(input)) <= precompileContractInputMetaDataLength {
113+
return nil, fmt.Errorf("invalid input: input should include %d bytes payload length and payload", precompileContractInputMetaDataLength)
114+
}
115+
116+
payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength])
117+
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength {
118+
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input))
119+
}
120+
121+
kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength:])
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
valid := kvmp.Validate()
127+
if !valid {
128+
return nil, fmt.Errorf("invalid merkle proof")
129+
}
130+
131+
result = make([]byte, merkleProofValidateResultLength)
132+
binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-8:], 0x01)
133+
return result, nil
134+
}

core/vm/contracts_lightclient_test.go

+104
Large diffs are not rendered by default.
+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package lightclient
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
7+
"github.com/tendermint/iavl"
8+
"github.com/tendermint/tendermint/crypto/merkle"
9+
cmn "github.com/tendermint/tendermint/libs/common"
10+
)
11+
12+
// MultiStoreProof defines a collection of store proofs in a multi-store
13+
type MultiStoreProof struct {
14+
StoreInfos []StoreInfo
15+
}
16+
17+
func NewMultiStoreProof(storeInfos []StoreInfo) *MultiStoreProof {
18+
return &MultiStoreProof{StoreInfos: storeInfos}
19+
}
20+
21+
// ComputeRootHash returns the root hash for a given multi-store proof.
22+
func (proof *MultiStoreProof) ComputeRootHash() []byte {
23+
ci := CommitInfo{
24+
Version: -1, // TODO: Not needed; improve code.
25+
StoreInfos: proof.StoreInfos,
26+
}
27+
return ci.Hash()
28+
}
29+
30+
// RequireProof return whether proof is require for the subpath
31+
func RequireProof(subpath string) bool {
32+
// XXX: create a better convention.
33+
// Currently, only when query subpath is "/store" or "/key", will proof be included in response.
34+
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212
35+
if subpath == "/store" || subpath == "/key" {
36+
return true
37+
}
38+
return false
39+
}
40+
41+
//-----------------------------------------------------------------------------
42+
43+
var _ merkle.ProofOperator = MultiStoreProofOp{}
44+
45+
// the multi-store proof operation constant value
46+
const ProofOpMultiStore = "multistore"
47+
48+
// TODO: document
49+
type MultiStoreProofOp struct {
50+
// Encoded in ProofOp.Key
51+
key []byte
52+
53+
// To encode in ProofOp.Data.
54+
Proof *MultiStoreProof `json:"proof"`
55+
}
56+
57+
func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp {
58+
return MultiStoreProofOp{
59+
key: key,
60+
Proof: proof,
61+
}
62+
}
63+
64+
// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a
65+
// given proof operation.
66+
func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
67+
if pop.Type != ProofOpMultiStore {
68+
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore)
69+
}
70+
71+
// XXX: a bit strange as we'll discard this, but it works
72+
var op MultiStoreProofOp
73+
74+
err := Cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op)
75+
if err != nil {
76+
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp")
77+
}
78+
79+
return NewMultiStoreProofOp(pop.Key, op.Proof), nil
80+
}
81+
82+
// ProofOp return a merkle proof operation from a given multi-store proof
83+
// operation.
84+
func (op MultiStoreProofOp) ProofOp() merkle.ProofOp {
85+
bz := Cdc.MustMarshalBinaryLengthPrefixed(op)
86+
return merkle.ProofOp{
87+
Type: ProofOpMultiStore,
88+
Key: op.key,
89+
Data: bz,
90+
}
91+
}
92+
93+
// String implements the Stringer interface for a mult-store proof operation.
94+
func (op MultiStoreProofOp) String() string {
95+
return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey())
96+
}
97+
98+
// GetKey returns the key for a multi-store proof operation.
99+
func (op MultiStoreProofOp) GetKey() []byte {
100+
return op.key
101+
}
102+
103+
// Run executes a multi-store proof operation for a given value. It returns
104+
// the root hash if the value matches all the store's commitID's hash or an
105+
// error otherwise.
106+
func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) {
107+
if len(args) != 1 {
108+
return nil, cmn.NewError("Value size is not 1")
109+
}
110+
111+
value := args[0]
112+
root := op.Proof.ComputeRootHash()
113+
114+
for _, si := range op.Proof.StoreInfos {
115+
if si.Name == string(op.key) {
116+
if bytes.Equal(value, si.Core.CommitID.Hash) {
117+
return [][]byte{root}, nil
118+
}
119+
120+
return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value)
121+
}
122+
}
123+
124+
return nil, cmn.NewError("key %v not found in multistore proof", op.key)
125+
}
126+
127+
//-----------------------------------------------------------------------------
128+
129+
// XXX: This should be managed by the rootMultiStore which may want to register
130+
// more proof ops?
131+
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
132+
prt = merkle.NewProofRuntime()
133+
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
134+
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder)
135+
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder)
136+
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
137+
return
138+
}

core/vm/lightclient/rootmultistore.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package lightclient
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/tendermint/tendermint/crypto/merkle"
7+
"github.com/tendermint/tendermint/crypto/tmhash"
8+
)
9+
10+
//----------------------------------------
11+
// CommitID
12+
13+
// CommitID contains the tree version number and its merkle root.
14+
type CommitID struct {
15+
Version int64
16+
Hash []byte
17+
}
18+
19+
func (cid CommitID) IsZero() bool { //nolint
20+
return cid.Version == 0 && len(cid.Hash) == 0
21+
}
22+
23+
func (cid CommitID) String() string {
24+
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version)
25+
}
26+
27+
//----------------------------------------
28+
// CommitInfo
29+
30+
// NOTE: Keep CommitInfo a simple immutable struct.
31+
type CommitInfo struct {
32+
33+
// Version
34+
Version int64
35+
36+
// Store info for
37+
StoreInfos []StoreInfo
38+
}
39+
40+
// Hash returns the simple merkle root hash of the stores sorted by name.
41+
func (ci CommitInfo) Hash() []byte {
42+
// TODO cache to ci.hash []byte
43+
m := make(map[string][]byte, len(ci.StoreInfos))
44+
for _, storeInfo := range ci.StoreInfos {
45+
m[storeInfo.Name] = storeInfo.Hash()
46+
}
47+
return merkle.SimpleHashFromMap(m)
48+
}
49+
50+
func (ci CommitInfo) CommitID() CommitID {
51+
return CommitID{
52+
Version: ci.Version,
53+
Hash: ci.Hash(),
54+
}
55+
}
56+
57+
//----------------------------------------
58+
// StoreInfo
59+
60+
// StoreInfo contains the name and core reference for an
61+
// underlying store. It is the leaf of the rootMultiStores top
62+
// level simple merkle tree.
63+
type StoreInfo struct {
64+
Name string
65+
Core StoreCore
66+
}
67+
68+
type StoreCore struct {
69+
// StoreType StoreType
70+
CommitID CommitID
71+
// ... maybe add more state
72+
}
73+
74+
// Implements merkle.Hasher.
75+
func (si StoreInfo) Hash() []byte {
76+
// Doesn't write Name, since merkle.SimpleHashFromMap() will
77+
// include them via the keys.
78+
bz, _ := Cdc.MarshalBinaryLengthPrefixed(si.Core) // Does not error
79+
hasher := tmhash.New()
80+
_, err := hasher.Write(bz)
81+
if err != nil {
82+
// TODO: Handle with #870
83+
panic(err)
84+
}
85+
return hasher.Sum(nil)
86+
}

0 commit comments

Comments
 (0)