-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from binance-chain/precompile-contract
R4R: implement precompile contract
- Loading branch information
Showing
11 changed files
with
1,120 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package vm | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
|
||
"github.com/ethereum/go-ethereum/core/vm/lightclient" | ||
"github.com/ethereum/go-ethereum/params" | ||
) | ||
|
||
const ( | ||
precompileContractInputMetaDataLength uint64 = 32 | ||
consensusStateLengthBytesLength uint64 = 32 | ||
|
||
tmHeaderValidateResultMetaDataLength uint64 = 32 | ||
merkleProofValidateResultLength uint64 = 32 | ||
) | ||
|
||
// input: | ||
// consensus state length | consensus state | tendermint header | | ||
// 32 bytes | | | | ||
func decodeTendermintHeaderValidationInput(input []byte) (*lightclient.ConsensusState, *lightclient.Header, error) { | ||
csLen := binary.BigEndian.Uint64(input[consensusStateLengthBytesLength-8 : consensusStateLengthBytesLength]) | ||
if uint64(len(input)) <= consensusStateLengthBytesLength+csLen { | ||
return nil, nil, fmt.Errorf("expected payload size %d, actual size: %d", consensusStateLengthBytesLength+csLen, len(input)) | ||
} | ||
|
||
cs, err := lightclient.DecodeConsensusState(input[consensusStateLengthBytesLength : consensusStateLengthBytesLength+csLen]) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
header, err := lightclient.DecodeHeader(input[consensusStateLengthBytesLength+csLen:]) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
return &cs, header, nil | ||
} | ||
|
||
// tmHeaderValidate implemented as a native contract. | ||
type tmHeaderValidate struct{} | ||
|
||
func (c *tmHeaderValidate) RequiredGas(input []byte) uint64 { | ||
return params.TendermintHeaderValidateGas | ||
} | ||
|
||
func (c *tmHeaderValidate) Run(input []byte) (result []byte, err error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
err = fmt.Errorf("internal error: %v\n", r) | ||
} | ||
}() | ||
|
||
if uint64(len(input)) <= precompileContractInputMetaDataLength { | ||
return nil, fmt.Errorf("invalid input") | ||
} | ||
|
||
payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength]) | ||
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength { | ||
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input)) | ||
} | ||
|
||
cs, header, err := decodeTendermintHeaderValidationInput(input[precompileContractInputMetaDataLength:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
validatorSetChanged, err := cs.ApplyHeader(header) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
consensusStateBytes, err := cs.EncodeConsensusState() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// result | ||
// | validatorSetChanged | empty | consensusStateBytesLength | new consensusState | | ||
// | 1 byte | 23 bytes | 8 bytes | | | ||
lengthBytes := make([]byte, tmHeaderValidateResultMetaDataLength) | ||
if validatorSetChanged { | ||
copy(lengthBytes[:1], []byte{0x01}) | ||
} | ||
consensusStateBytesLength := uint64(len(consensusStateBytes)) | ||
binary.BigEndian.PutUint64(lengthBytes[tmHeaderValidateResultMetaDataLength-8:], consensusStateBytesLength) | ||
|
||
result = append(lengthBytes, consensusStateBytes...) | ||
|
||
return result, nil | ||
} | ||
|
||
//------------------------------------------------------------------------------------------------------------------------------------------------ | ||
|
||
// tmHeaderValidate implemented as a native contract. | ||
type iavlMerkleProofValidate struct{} | ||
|
||
func (c *iavlMerkleProofValidate) RequiredGas(input []byte) uint64 { | ||
return params.IAVLMerkleProofValidateGas | ||
} | ||
|
||
// input: | ||
// | payload length | payload | | ||
// | 32 bytes | | | ||
func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
err = fmt.Errorf("internal error: %v\n", r) | ||
} | ||
}() | ||
|
||
if uint64(len(input)) <= precompileContractInputMetaDataLength { | ||
return nil, fmt.Errorf("invalid input: input should include %d bytes payload length and payload", precompileContractInputMetaDataLength) | ||
} | ||
|
||
payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength]) | ||
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength { | ||
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input)) | ||
} | ||
|
||
kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
valid := kvmp.Validate() | ||
if !valid { | ||
return nil, fmt.Errorf("invalid merkle proof") | ||
} | ||
|
||
result = make([]byte, merkleProofValidateResultLength) | ||
binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-8:], 0x01) | ||
return result, nil | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package lightclient | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
|
||
"github.com/tendermint/iavl" | ||
"github.com/tendermint/tendermint/crypto/merkle" | ||
cmn "github.com/tendermint/tendermint/libs/common" | ||
) | ||
|
||
// MultiStoreProof defines a collection of store proofs in a multi-store | ||
type MultiStoreProof struct { | ||
StoreInfos []StoreInfo | ||
} | ||
|
||
func NewMultiStoreProof(storeInfos []StoreInfo) *MultiStoreProof { | ||
return &MultiStoreProof{StoreInfos: storeInfos} | ||
} | ||
|
||
// ComputeRootHash returns the root hash for a given multi-store proof. | ||
func (proof *MultiStoreProof) ComputeRootHash() []byte { | ||
ci := CommitInfo{ | ||
Version: -1, // TODO: Not needed; improve code. | ||
StoreInfos: proof.StoreInfos, | ||
} | ||
return ci.Hash() | ||
} | ||
|
||
// RequireProof return whether proof is require for the subpath | ||
func RequireProof(subpath string) bool { | ||
// XXX: create a better convention. | ||
// Currently, only when query subpath is "/store" or "/key", will proof be included in response. | ||
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212 | ||
if subpath == "/store" || subpath == "/key" { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
//----------------------------------------------------------------------------- | ||
|
||
var _ merkle.ProofOperator = MultiStoreProofOp{} | ||
|
||
// the multi-store proof operation constant value | ||
const ProofOpMultiStore = "multistore" | ||
|
||
// TODO: document | ||
type MultiStoreProofOp struct { | ||
// Encoded in ProofOp.Key | ||
key []byte | ||
|
||
// To encode in ProofOp.Data. | ||
Proof *MultiStoreProof `json:"proof"` | ||
} | ||
|
||
func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp { | ||
return MultiStoreProofOp{ | ||
key: key, | ||
Proof: proof, | ||
} | ||
} | ||
|
||
// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a | ||
// given proof operation. | ||
func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { | ||
if pop.Type != ProofOpMultiStore { | ||
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore) | ||
} | ||
|
||
// XXX: a bit strange as we'll discard this, but it works | ||
var op MultiStoreProofOp | ||
|
||
err := Cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) | ||
if err != nil { | ||
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp") | ||
} | ||
|
||
return NewMultiStoreProofOp(pop.Key, op.Proof), nil | ||
} | ||
|
||
// ProofOp return a merkle proof operation from a given multi-store proof | ||
// operation. | ||
func (op MultiStoreProofOp) ProofOp() merkle.ProofOp { | ||
bz := Cdc.MustMarshalBinaryLengthPrefixed(op) | ||
return merkle.ProofOp{ | ||
Type: ProofOpMultiStore, | ||
Key: op.key, | ||
Data: bz, | ||
} | ||
} | ||
|
||
// String implements the Stringer interface for a mult-store proof operation. | ||
func (op MultiStoreProofOp) String() string { | ||
return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey()) | ||
} | ||
|
||
// GetKey returns the key for a multi-store proof operation. | ||
func (op MultiStoreProofOp) GetKey() []byte { | ||
return op.key | ||
} | ||
|
||
// Run executes a multi-store proof operation for a given value. It returns | ||
// the root hash if the value matches all the store's commitID's hash or an | ||
// error otherwise. | ||
func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) { | ||
if len(args) != 1 { | ||
return nil, cmn.NewError("Value size is not 1") | ||
} | ||
|
||
value := args[0] | ||
root := op.Proof.ComputeRootHash() | ||
|
||
for _, si := range op.Proof.StoreInfos { | ||
if si.Name == string(op.key) { | ||
if bytes.Equal(value, si.Core.CommitID.Hash) { | ||
return [][]byte{root}, nil | ||
} | ||
|
||
return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value) | ||
} | ||
} | ||
|
||
return nil, cmn.NewError("key %v not found in multistore proof", op.key) | ||
} | ||
|
||
//----------------------------------------------------------------------------- | ||
|
||
// XXX: This should be managed by the rootMultiStore which may want to register | ||
// more proof ops? | ||
func DefaultProofRuntime() (prt *merkle.ProofRuntime) { | ||
prt = merkle.NewProofRuntime() | ||
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder) | ||
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder) | ||
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder) | ||
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package lightclient | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/tendermint/tendermint/crypto/merkle" | ||
"github.com/tendermint/tendermint/crypto/tmhash" | ||
) | ||
|
||
//---------------------------------------- | ||
// CommitID | ||
|
||
// CommitID contains the tree version number and its merkle root. | ||
type CommitID struct { | ||
Version int64 | ||
Hash []byte | ||
} | ||
|
||
func (cid CommitID) IsZero() bool { //nolint | ||
return cid.Version == 0 && len(cid.Hash) == 0 | ||
} | ||
|
||
func (cid CommitID) String() string { | ||
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version) | ||
} | ||
|
||
//---------------------------------------- | ||
// CommitInfo | ||
|
||
// NOTE: Keep CommitInfo a simple immutable struct. | ||
type CommitInfo struct { | ||
|
||
// Version | ||
Version int64 | ||
|
||
// Store info for | ||
StoreInfos []StoreInfo | ||
} | ||
|
||
// Hash returns the simple merkle root hash of the stores sorted by name. | ||
func (ci CommitInfo) Hash() []byte { | ||
// TODO cache to ci.hash []byte | ||
m := make(map[string][]byte, len(ci.StoreInfos)) | ||
for _, storeInfo := range ci.StoreInfos { | ||
m[storeInfo.Name] = storeInfo.Hash() | ||
} | ||
return merkle.SimpleHashFromMap(m) | ||
} | ||
|
||
func (ci CommitInfo) CommitID() CommitID { | ||
return CommitID{ | ||
Version: ci.Version, | ||
Hash: ci.Hash(), | ||
} | ||
} | ||
|
||
//---------------------------------------- | ||
// StoreInfo | ||
|
||
// StoreInfo contains the name and core reference for an | ||
// underlying store. It is the leaf of the rootMultiStores top | ||
// level simple merkle tree. | ||
type StoreInfo struct { | ||
Name string | ||
Core StoreCore | ||
} | ||
|
||
type StoreCore struct { | ||
// StoreType StoreType | ||
CommitID CommitID | ||
// ... maybe add more state | ||
} | ||
|
||
// Implements merkle.Hasher. | ||
func (si StoreInfo) Hash() []byte { | ||
// Doesn't write Name, since merkle.SimpleHashFromMap() will | ||
// include them via the keys. | ||
bz, _ := Cdc.MarshalBinaryLengthPrefixed(si.Core) // Does not error | ||
hasher := tmhash.New() | ||
_, err := hasher.Write(bz) | ||
if err != nil { | ||
// TODO: Handle with #870 | ||
panic(err) | ||
} | ||
return hasher.Sum(nil) | ||
} |
Oops, something went wrong.