Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some docs to the payment module #187

Merged
merged 17 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions app/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,13 @@ func generateRawTx(t *testing.T, txConfig client.TxConfig, ns, message []byte, r
msg := generateSignedWirePayForMessage(t, consts.MaxSquareSize, ns, message, ring)

krs := generateKeyringSigner(t, "test")
builder := krs.NewTxBuilder()

coin := sdk.Coin{
Denom: "token",
Amount: sdk.NewInt(1000),
}

builder.SetFeeAmount(sdk.NewCoins(coin))
builder.SetGasLimit(10000)
builder.SetTimeoutHeight(99)

tx, err := krs.BuildSignedTx(builder, msg)
tx, err := krs.BuildSignedTx(msg, types.SetFeeAmount(sdk.NewCoins(coin)), types.SetGasLimit(200000))
require.NoError(t, err)

// encode the tx
Expand Down
138 changes: 138 additions & 0 deletions x/payment/spec/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
## Abstract

The payment module is responsible for paying for arbitrary data that will be added to the Celestia blockchain. While the data being submitted can be arbitrary, the exact placement of that data is important for the transaction to be valid. This is why the payment module utilizes a malleated transaction scheme. Malleated transactions allow for users to create a single transaction, that can later be malleated by the block producer to create a variety of different valid transactions that are still signed over by the user. To accomplish this, users create a single `MsgWirePayForMessage` transaction, which is composed of metadata and signatures for multiple variations of the transaction that will be included onchain. After the transaction is submitted to the network, the block producer selects the appropriate signature and creates a valid `MsgPayForMessage` transaction depending on the square size for that block. This new malleated `MsgPayForMessage` transaction is what ends up onchain.

Further reading: [Message Block Layout](https://github.com/celestiaorg/celestia-specs/blob/master/src/rationale/message_block_layout.md)

## State
- The sender’s account balance, via the bank keeper’s [`Burn`](https://github.com/cosmos/cosmos-sdk/blob/master/x/bank/spec/01_state.md) method.
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
- The standard incrememnt of the sender's account number via the [auth module](https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/spec/02_state.md#accounts).

## Messages
- [`MsgWirePayForMessage`](https://github.com/celestiaorg/celestia-app/blob/b4c8ebdf35db200a9b99d295a13de01110802af4/x/payment/types/tx.pb.go#L32-L40)

While this transaction is created and signed by the user, it never actually ends up onchain. Instead, it is used to create a new "malleated" transaction that does get included onchain.
- [`MsgPayForMessage`](https://github.com/celestiaorg/celestia-app/blob/b4c8ebdf35db200a9b99d295a13de01110802af4/x/payment/types/tx.pb.go#L208-L216)

The malleated transaction that is created from metadata contained in the original `MsgWirePayForMessage`. It also burns some of the sender’s funds.

## PreProcessTxs
The malleation process occurs during the PreProcessTxs step.
```go
// ProcessWirePayForMessage will perform the processing required by PreProcessTxs.
// It parses the MsgWirePayForMessage to produce the components needed to create a
// single MsgPayForMessage
func ProcessWirePayForMessage(msg *MsgWirePayForMessage, squareSize uint64) (*tmproto.Message, *MsgPayForMessage, []byte, error) {
// make sure that a ShareCommitAndSignature of the correct size is
// included in the message
var shareCommit *ShareCommitAndSignature
for _, commit := range msg.MessageShareCommitment {
if commit.K == squareSize {
shareCommit = &commit
}
}
if shareCommit == nil {
return nil,
nil,
nil,
fmt.Errorf("message does not commit to current square size: %d", squareSize)
}

// add the message to the list of core message to be returned to ll-core
coreMsg := tmproto.Message{
NamespaceId: msg.GetMessageNameSpaceId(),
Data: msg.GetMessage(),
}

// wrap the signed transaction data
pfm, err := msg.unsignedPayForMessage(squareSize)
if err != nil {
return nil, nil, nil, err
}

return &coreMsg, pfm, shareCommit.Signature, nil
}

// PreprocessTxs fulfills the celestia-core version of the ABCI interface, by
// performing basic validation for the incoming txs, and by cleanly separating
// share messages from transactions
func (app *App) PreprocessTxs(txs abci.RequestPreprocessTxs) abci.ResponsePreprocessTxs {
squareSize := app.SquareSize()
var shareMsgs []*core.Message
var processedTxs [][]byte
for _, rawTx := range txs.Txs {
// boiler plate
...
// parse wire message and create a single message
coreMsg, unsignedPFM, sig, err := types.ProcessWirePayForMessage(wireMsg, app.SquareSize())
if err != nil {
continue
}

// create the signed PayForMessage using the fees, gas limit, and sequence from
// the original transaction, along with the appropriate signature.
signedTx, err := types.BuildPayForMessageTxFromWireTx(authTx, app.txConfig.NewTxBuilder(), sig, unsignedPFM)
if err != nil {
app.Logger().Error("failure to create signed PayForMessage", err)
continue
}
...
// boiler plate
}
```

## Events
TODO after events are added.

## Parameters
There are no parameters yet, but we might add
- BaseFee
- SquareSize
- ShareSize
evan-forbes marked this conversation as resolved.
Show resolved Hide resolved

### Usage
`celestia-app tx payment payForMessage <hex encoded namespace> <hex encoded data> [flags]`

### Programmatic Usage
There are tools to programmatically create, sign, and broadcast `MsgWirePayForMessages`
```go
// use a keyring to sign messages programmatically
keyringSigner, err := NewKeyringSigner(keyring, "keyring account name", "chain-id-1")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im guessing this is the keyringsinger of this module and not the general sdk. Would be good to mention as this is the most confusing part.

May be good to open a issue on how to delete this code too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, added some more details that hopefully make this clearer. Also created an issue to look into different ways to sign malleated transactions. #192 4f740a4

if err != nil {
return err
}

// Query for account information necessary to sign a valid tx
err = keyringSigner.QueryAccount(ctx, grpcClientConn)
if err != nil {
return err
}

// create the raw WirePayForMessage transaction
wpfmMsg, err := apptypes.NewWirePayForMessage(block.Header.NamespaceId, message, 16, 32, 64, 128)
if err != nil {
return err
}

// create and sign the commitments to the data for all the the square sizes
gasLimOption := types.SetGasLimit(200000)
err = pfmMsg.SignShareCommitments(keyringSigner, gasLimOption)
if err != nil {
return err
}

signedTx, err := keyringSigner.BuildSignedTx(wpfmMsg, gasLimOption)
if err != nil {
return err
}
```

### How the commitments are generated
1) create the final version of the message by adding the length delimiter, the namespace, and then the message together into a single string of bytes
```
finalMessage = [length delimiter] + [namespace] + [message]
```
2) chunk the finalMessage into shares of size `consts.ShareSize`
3) pad until number of shares is a power of two
4) create the commitment by aranging the shares into a merkle mountain range
5) create a merkle root of the subtree roots
7 changes: 6 additions & 1 deletion x/payment/types/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ func (k *KeyringSigner) NewTxBuilder() sdkclient.TxBuilder {
// BuildSignedTx creates and signs a sdk.Tx that contains the provided message. The interal
// account number must be set by calling k.QueryAccountNumber or by manually setting it via
// k.SetAccountNumber for the built transactions to be valid.
func (k *KeyringSigner) BuildSignedTx(builder sdkclient.TxBuilder, msg sdktypes.Msg) (authsigning.Tx, error) {
func (k *KeyringSigner) BuildSignedTx(msg sdktypes.Msg, options ...TxBuilderOption) (authsigning.Tx, error) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this slight refactor isn't really inside the scope of this PR, I decided to include it anyway, as it is very minor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we pull this out or can we add this into the pr description. In the future if we need to reference this PR for this code it could get confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulled this out, and will add it in a different PR

k.RLock()
accountNumber := k.accountNumber
sequence := k.sequence
k.RUnlock()

builder := k.NewTxBuilder()
for _, option := range options {
builder = option(builder)
}

// set the msg
err := builder.SetMsgs(msg)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions x/payment/types/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestBuildWirePayForMessage(t *testing.T) {
msg, err := NewWirePayForMessage(namespace, message, 4, 16, 32)
require.NoError(t, err)

signedTx, err := k.BuildSignedTx(k.NewTxBuilder(), msg)
signedTx, err := k.BuildSignedTx(msg)
require.NoError(t, err)

rawTx, err := makeEncodingConfig().TxConfig.TxEncoder()(signedTx)
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestBroadcastPayForMessage(t *testing.T) {
msg, err := NewWirePayForMessage(namespace, message, 4, 16, 32)
require.NoError(t, err)

signedTx, err := k.BuildSignedTx(builder, msg)
signedTx, err := k.BuildSignedTx(msg)
require.NoError(t, err)

encodedTx, err := k.EncodeTx(signedTx)
Expand Down
11 changes: 1 addition & 10 deletions x/payment/types/payformessage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"testing"

sdkclient "github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
Expand Down Expand Up @@ -189,8 +188,7 @@ func TestSignMalleatedTxs(t *testing.T) {
require.NoError(t, err)

// create a new tx builder to create an unsigned PayForMessage
builder := applyOptions(signer.NewTxBuilder(), tt.options...)
tx, err := signer.BuildSignedTx(builder, unsignedPFM)
tx, err := signer.BuildSignedTx(unsignedPFM, tt.options...)
require.NoError(t, err)

// Generate the bytes to be signed.
Expand Down Expand Up @@ -386,10 +384,3 @@ func validWirePayForMessage(t *testing.T) *MsgWirePayForMessage {
}
return msg
}

func applyOptions(builder sdkclient.TxBuilder, options ...TxBuilderOption) sdkclient.TxBuilder {
for _, option := range options {
builder = option(builder)
}
return builder
}
13 changes: 3 additions & 10 deletions x/payment/types/wirepayformessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
fmt "fmt"

sdkclient "github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
Expand Down Expand Up @@ -45,13 +44,7 @@ func (msg *MsgWirePayForMessage) SignShareCommitments(signer *KeyringSigner, opt
msg.Signer = signer.GetSignerInfo().GetAddress().String()
// create an entire MsgPayForMessage and signing over it, including the signature in each commitment
for i, commit := range msg.MessageShareCommitment {
builder := signer.NewTxBuilder()

for _, option := range options {
builder = option(builder)
}

sig, err := msg.createPayForMessageSignature(signer, builder, commit.K)
sig, err := msg.createPayForMessageSignature(signer, commit.K, options...)
if err != nil {
return err
}
Expand Down Expand Up @@ -132,12 +125,12 @@ func (msg *MsgWirePayForMessage) GetSigners() []sdk.AccAddress {

// createPayForMessageSignature generates the signature for a PayForMessage for a single square
// size using the info from a MsgWirePayForMessage
func (msg *MsgWirePayForMessage) createPayForMessageSignature(signer *KeyringSigner, builder sdkclient.TxBuilder, k uint64) ([]byte, error) {
func (msg *MsgWirePayForMessage) createPayForMessageSignature(signer *KeyringSigner, k uint64, options ...TxBuilderOption) ([]byte, error) {
pfm, err := msg.unsignedPayForMessage(k)
if err != nil {
return nil, err
}
tx, err := signer.BuildSignedTx(builder, pfm)
tx, err := signer.BuildSignedTx(pfm, options...)
if err != nil {
return nil, err
}
Expand Down