forked from cosmos/ibc-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lazyledger DA client implementation (cosmos#81)
* Initial version of lazyledger DA client Kudos to Evan for unblocking me on gRPC issues. Co-authored-by: evan-forbes <[email protected]>
- Loading branch information
1 parent
fca8429
commit f436e3f
Showing
4 changed files
with
502 additions
and
28 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package lazyledger | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"time" | ||
|
||
"github.com/pelletier/go-toml" | ||
"google.golang.org/grpc" | ||
|
||
"github.com/cosmos/cosmos-sdk/crypto/keyring" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/types/tx" | ||
appclient "github.com/lazyledger/lazyledger-app/x/lazyledgerapp/client" | ||
apptypes "github.com/lazyledger/lazyledger-app/x/lazyledgerapp/types" | ||
|
||
"github.com/lazyledger/optimint/da" | ||
"github.com/lazyledger/optimint/log" | ||
"github.com/lazyledger/optimint/types" | ||
) | ||
|
||
type Config struct { | ||
// PayForMessage related params | ||
NamespaceID []byte | ||
PubKey []byte | ||
BaseRateMax uint64 // currently not used | ||
TipRateMax uint64 // currently not used | ||
|
||
// temporary fee fields | ||
GasLimit uint64 | ||
FeeAmount uint64 | ||
|
||
// RPC related params | ||
RPCAddress string | ||
ChainID string | ||
Timeout time.Duration | ||
|
||
// keyring related params | ||
|
||
// KeyringAccName is the name of the account registered in the keyring | ||
// for the `From` address field | ||
KeyringAccName string | ||
// Backend is the backend of keyring that contains the KeyringAccName | ||
Backend string | ||
RootDir string | ||
} | ||
|
||
type LazyLedger struct { | ||
config Config | ||
logger log.Logger | ||
|
||
keyring keyring.Keyring | ||
|
||
rpcClient *grpc.ClientConn | ||
} | ||
|
||
var _ da.DataAvailabilityLayerClient = &LazyLedger{} | ||
|
||
// Init is called once to allow DA client to read configuration and initialize resources. | ||
func (ll *LazyLedger) Init(config []byte, logger log.Logger) error { | ||
ll.logger = logger | ||
err := toml.Unmarshal(config, &ll.config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// TODO(tzdybal): this means interactive reading from stdin - shouldn't we replace this somehow? | ||
userInput := os.Stdin | ||
ll.keyring, err = keyring.New(ll.config.KeyringAccName, ll.config.Backend, ll.config.RootDir, userInput) | ||
return err | ||
} | ||
|
||
func (ll *LazyLedger) Start() (err error) { | ||
ll.rpcClient, err = grpc.Dial(ll.config.RPCAddress, grpc.WithInsecure()) | ||
return | ||
} | ||
|
||
func (ll *LazyLedger) Stop() error { | ||
return ll.rpcClient.Close() | ||
} | ||
|
||
// SubmitBlock submits the passed in block to the DA layer. | ||
// This should create a transaction which (potentially) | ||
// triggers a state transition in the DA layer. | ||
func (ll *LazyLedger) SubmitBlock(block *types.Block) da.ResultSubmitBlock { | ||
msg, err := ll.preparePayForMessage(block) | ||
if err != nil { | ||
return da.ResultSubmitBlock{Code: da.StatusError, Message: err.Error()} | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(context.TODO(), ll.config.Timeout) | ||
defer cancel() | ||
|
||
err = ll.callRPC(ctx, msg) | ||
if err != nil { | ||
return da.ResultSubmitBlock{Code: da.StatusError, Message: err.Error()} | ||
} | ||
|
||
return da.ResultSubmitBlock{Code: da.StatusSuccess} | ||
} | ||
|
||
func (ll *LazyLedger) callRPC(ctx context.Context, msg *apptypes.MsgWirePayForMessage) error { | ||
signer := appclient.NewKeyringSigner(ll.keyring, ll.config.KeyringAccName, ll.config.ChainID) | ||
|
||
err := signer.QueryAccountNumber(ctx, ll.rpcClient) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
coin := sdk.Coin{ | ||
Denom: "token", | ||
Amount: sdk.NewInt(10), // TODO(tzdybal): un-hardcode | ||
} | ||
|
||
builder := signer.NewTxBuilder() | ||
builder.SetFeeAmount(sdk.NewCoins(coin)) | ||
builder.SetGasLimit(ll.config.GasLimit) | ||
|
||
signedTx, err := signer.BuildSignedTx(builder, msg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rawTx, err := signer.EncodeTx(signedTx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// wait for the transaction to be confirmed in a block | ||
_, err = appclient.BroadcastTx(ctx, ll.rpcClient, tx.BroadcastMode_BROADCAST_MODE_BLOCK, rawTx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (ll *LazyLedger) preparePayForMessage(block *types.Block) (*apptypes.MsgWirePayForMessage, error) { | ||
// TODO(tzdybal): serialize block | ||
var message []byte | ||
message, err := block.MarshalBinary() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// create PayForMessage message | ||
msg, err := apptypes.NewMsgWirePayForMessage( | ||
ll.config.NamespaceID, | ||
message, | ||
ll.config.PubKey, | ||
&apptypes.TransactionFee{ | ||
BaseRateMax: ll.config.BaseRateMax, | ||
TipRateMax: ll.config.TipRateMax, | ||
}, | ||
apptypes.SquareSize, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// sign the PayForMessage's ShareCommitments | ||
err = msg.SignShareCommitments(ll.config.KeyringAccName, ll.keyring) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return msg, msg.ValidateBasic() | ||
} |
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,114 @@ | ||
package lazyledger | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/crypto/hd" | ||
"github.com/cosmos/cosmos-sdk/crypto/keyring" | ||
"github.com/lazyledger/optimint/da" | ||
"github.com/lazyledger/optimint/types" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConfiguration(t *testing.T) { | ||
t.Parallel() | ||
cases := []struct { | ||
name string | ||
input []byte | ||
err error | ||
expected Config | ||
}{ | ||
{"empty config", []byte(""), errors.New("unknown keyring backend "), Config{}}, | ||
{"with namespace id", []byte("NamespaceID = [3, 2, 1]\nBackend = 'test'"), nil, Config{NamespaceID: []byte{0x03, 0x02, 0x01}, Backend: "test"}}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.name, func(t *testing.T) { | ||
assert := assert.New(t) | ||
ll := &LazyLedger{} | ||
err := ll.Init(c.input, nil) | ||
|
||
if c.err != nil { | ||
assert.EqualError(err, c.err.Error()) | ||
} else { | ||
assert.NoError(err) | ||
assert.Equal(c.expected, ll.config) | ||
} | ||
|
||
}) | ||
} | ||
} | ||
|
||
func TestSubmission(t *testing.T) { | ||
t.Skip("this test requires configured and running lazyledger-appd") | ||
assert := assert.New(t) | ||
require := require.New(t) | ||
block := &types.Block{Header: types.Header{ | ||
Height: 1, | ||
}} | ||
|
||
ll := &LazyLedger{} | ||
kr := generateKeyring(t) | ||
key, err := kr.Key("test-account") | ||
require.NoError(err) | ||
conf := testConfig(key) | ||
err = ll.Init([]byte(conf), nil) | ||
require.NoError(err) | ||
ll.keyring = kr | ||
|
||
err = ll.Start() | ||
require.NoError(err) | ||
|
||
result := ll.SubmitBlock(block) | ||
assert.Equal("", result.Message) | ||
assert.Equal(da.StatusSuccess, result.Code) | ||
} | ||
|
||
// nolint: unused | ||
func testConfig(key keyring.Info) string { | ||
keyStr := "" | ||
for _, b := range key.GetPubKey().Bytes() { | ||
keyStr += strconv.Itoa(int(b)) + ", " | ||
} | ||
conf := fmt.Sprintf(`PubKey=[%s] | ||
Backend = 'test' | ||
From = '%s' | ||
KeyringAccName = 'test-account' | ||
RPCAddress = '127.0.0.1:9090' | ||
NamespaceID = [3, 2, 1, 0, 3, 2, 1, 0] | ||
GasLimit = 100000 | ||
FeeAmount = 1 | ||
ChainID = 'test' | ||
`, keyStr, key.GetAddress().String()) | ||
return conf | ||
} | ||
|
||
// nolint: unused | ||
func generateKeyring(t *testing.T, accts ...string) keyring.Keyring { | ||
t.Helper() | ||
kb := keyring.NewInMemory() | ||
|
||
for _, acc := range accts { | ||
_, _, err := kb.NewMnemonic(acc, keyring.English, "", hd.Secp256k1) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
} | ||
|
||
_, err := kb.NewAccount(testAccName, testMnemo, "1234", "", hd.Secp256k1) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return kb | ||
} | ||
|
||
const ( | ||
testMnemo = `ramp soldier connect gadget domain mutual staff unusual first midnight iron good deputy wage vehicle mutual spike unlock rocket delay hundred script tumble choose` | ||
testAccName = "test-account" | ||
) |
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
Oops, something went wrong.