Skip to content

Commit

Permalink
added extensible auth using build tags (#339)
Browse files Browse the repository at this point in the history
* added extensible auth using build tags

* go mod tidied

* made gavins requested changes
  • Loading branch information
brennanjl authored and charithabandi committed Oct 9, 2023
1 parent 67fe11e commit 6c91804
Show file tree
Hide file tree
Showing 21 changed files with 610 additions and 51 deletions.
12 changes: 10 additions & 2 deletions internal/app/kwild/server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func buildServer(d *coreDependencies, closers *closeFuncs) *Server {

bootstrapperModule := buildBootstrapper(d)

abciApp := buildAbci(d, closers, datasetsModule, validatorModule,
abciApp := buildAbci(d, closers, accs, datasetsModule, validatorModule,
ac, snapshotModule, bootstrapperModule)

cometBftNode := buildCometNode(d, closers, abciApp)
Expand Down Expand Up @@ -132,7 +132,7 @@ func (c *closeFuncs) closeAll() error {
return err
}

func buildAbci(d *coreDependencies, closer *closeFuncs, datasetsModule abci.DatasetsModule, validatorModule abci.ValidatorModule,
func buildAbci(d *coreDependencies, closer *closeFuncs, accountsModule abci.AccountsModule, datasetsModule abci.DatasetsModule, validatorModule abci.ValidatorModule,
atomicCommitter *sessions.AtomicCommitter, snapshotter *snapshots.SnapshotStore, bootstrapper *snapshots.Bootstrapper) *abci.AbciApp {
badgerPath := filepath.Join(d.cfg.RootDir, abciDirName, kwild.ABCIInfoSubDirName)
badgerKv, err := badger.NewBadgerDB(d.ctx, badgerPath, &badger.Options{
Expand All @@ -157,6 +157,7 @@ func buildAbci(d *coreDependencies, closer *closeFuncs, datasetsModule abci.Data

genesisHash := d.genesisCfg.ComputeGenesisHash()
return abci.NewAbciApp(
accountsModule,
datasetsModule,
validatorModule,
atomicKv,
Expand All @@ -165,6 +166,7 @@ func buildAbci(d *coreDependencies, closer *closeFuncs, datasetsModule abci.Data
bootstrapper,
genesisHash,
abci.WithLogger(*d.log.Named("abci")),
abci.WithoutGasCosts(d.genesisCfg.ConsensusParams.WithoutGasCosts),
)
}

Expand Down Expand Up @@ -251,9 +253,15 @@ func buildValidatorManager(d *coreDependencies, closer *closeFuncs, ac *sessions
closer.addCloser(db.Close)

joinExpiry := d.genesisCfg.ConsensusParams.Validator.JoinExpiry
feeMultiplier := 1
if d.genesisCfg.ConsensusParams.WithoutGasCosts {
feeMultiplier = 0
}

v, err := vmgr.NewValidatorMgr(d.ctx, db,
vmgr.WithLogger(*d.log.Named("validatorStore")),
vmgr.WithJoinExpiry(joinExpiry),
vmgr.WithFeeMultiplier(int64(feeMultiplier)),
)
if err != nil {
failBuild(err, "failed to build validator store")
Expand Down
6 changes: 3 additions & 3 deletions internal/controller/grpc/txsvc/v1/pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ func (s *Service) priceAction(ctx context.Context, txBody *txpb.Transaction_Body

// TODO: Later to be moved to validator module (or) manager
func (s *Service) priceValidatorJoin(ctx context.Context, txBody *txpb.Transaction_Body) (*big.Int, error) {
return big.NewInt(10000000000000), nil
return s.vstore.PriceJoin(ctx)
}

func (s *Service) priceValidatorLeave(ctx context.Context, txBody *txpb.Transaction_Body) (*big.Int, error) {
return big.NewInt(10000000000000), nil
return s.vstore.PriceLeave(ctx)
}

func (s *Service) priceValidatorApprove(ctx context.Context, txBody *txpb.Transaction_Body) (*big.Int, error) {
return big.NewInt(10000000000000), nil
return s.vstore.PriceApprove(ctx)
}
3 changes: 3 additions & 0 deletions internal/controller/grpc/txsvc/v1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@ type ValidatorReader interface {
CurrentValidators(ctx context.Context) ([]*validators.Validator, error)
ActiveVotes(ctx context.Context) ([]*validators.JoinRequest, error)
// JoinStatus(ctx context.Context, joiner []byte) ([]*JoinRequest, error)
PriceJoin(ctx context.Context) (*big.Int, error)
PriceLeave(ctx context.Context) (*big.Int, error)
PriceApprove(ctx context.Context) (*big.Int, error)
}
149 changes: 134 additions & 15 deletions pkg/abci/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/big"
"sort"

"github.com/kwilteam/kwil-db/pkg/crypto"
Expand Down Expand Up @@ -62,7 +63,7 @@ func (ds nilStringer) String() string {
return "no message"
}

func NewAbciApp(database DatasetsModule, vldtrs ValidatorModule, kv KVStore, committer AtomicCommitter, snapshotter SnapshotModule,
func NewAbciApp(accounts AccountsModule, database DatasetsModule, vldtrs ValidatorModule, kv KVStore, committer AtomicCommitter, snapshotter SnapshotModule,
bootstrapper DBBootstrapModule, genesisHash []byte, opts ...AbciOpt) *AbciApp {
app := &AbciApp{
genesisAppHash: genesisHash,
Expand All @@ -72,11 +73,18 @@ func NewAbciApp(database DatasetsModule, vldtrs ValidatorModule, kv KVStore, com
metadataStore: &metadataStore{
kv: kv,
},
bootstrapper: bootstrapper,
snapshotter: snapshotter,
bootstrapper: bootstrapper,
snapshotter: snapshotter,
accounts: accounts,
withoutGasCosts: true,

valAddrToKey: make(map[string][]byte),

mempool: &mempool{
nonceTracker: make(map[string]uint64),
accounts: make(map[string]*userAccount),
},

log: log.NewNoOp(),
}

Expand Down Expand Up @@ -120,9 +128,18 @@ type AbciApp struct {
snapshotter SnapshotModule

// bootstrapper is the bootstrapper module that handles bootstrapping the database
bootstrapper DBBootstrapModule
bootstrapper DBBootstrapModule

// metadataStore to track the app hash and block height
metadataStore *metadataStore

// accountStore is the store that maintains the account state
accounts AccountsModule
withoutGasCosts bool

// mempoolState maintains in-memory account state to validate the transactions against.
mempool *mempool

log log.Logger

// Expected AppState after bootstrapping the node with a given snapshot,
Expand Down Expand Up @@ -193,9 +210,11 @@ func (a *AbciApp) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Response
func (a *AbciApp) CheckTx(incoming abciTypes.RequestCheckTx) abciTypes.ResponseCheckTx {
logger := a.log.With(zap.String("stage", "ABCI CheckTx"))
logger.Debug("check tx")
ctx := context.Background()
var err error

tx := &transactions.Transaction{}
err := tx.UnmarshalBinary(incoming.Tx)
err = tx.UnmarshalBinary(incoming.Tx)
if err != nil {
logger.Error("failed to unmarshal transaction", zap.Error(err))
return abciTypes.ResponseCheckTx{Code: 1, Log: err.Error()}
Expand All @@ -205,21 +224,77 @@ func (a *AbciApp) CheckTx(incoming abciTypes.RequestCheckTx) abciTypes.ResponseC
zap.String("sender", hex.EncodeToString(tx.Sender)),
zap.String("PayloadType", tx.Body.PayloadType.String()))

err = tx.Verify()
// For a new transaction (not re-check), before looking at execution cost or
// checking nonce validity, ensure the payload is recognized and signature is valid.
if incoming.Type == abciTypes.CheckTxType_New {
// Verify Payload type
if !tx.Body.PayloadType.Valid() {
logger.Error("invalid payload type", zap.String("payloadType", tx.Body.PayloadType.String()))
return abciTypes.ResponseCheckTx{Code: 1, Log: "invalid payload type"}
}

// Verify Signature
err = tx.Verify()
if err != nil {
logger.Error("failed to verify transaction", zap.Error(err))
return abciTypes.ResponseCheckTx{Code: 1, Log: err.Error()}
}

// Estimate price of the transaction based on the payload type
if !a.withoutGasCosts {
var price *big.Int
price, err = a.estimateTxPrice(ctx, tx)
if err != nil {
logger.Error("failed to estimate tx price", zap.Error(err))
return abciTypes.ResponseCheckTx{Code: 1, Log: err.Error()}
}

// Check if tx fee is enough to cover the gas costs
if tx.Body.Fee.Cmp(price) < 0 {
logger.Error("insufficient fee", zap.String("fee", tx.Body.Fee.String()), zap.String("price", price.String()))
return abciTypes.ResponseCheckTx{Code: 1, Log: "insufficient fee"}
}
}
}

a.mempool.mu.Lock()
defer a.mempool.mu.Unlock()

acct, err := a.accountInfo(ctx, tx.Sender)
if err != nil {
logger.Error("failed to verify transaction", zap.Error(err))
logger.Error("failed to get account info", zap.Error(err))
return abciTypes.ResponseCheckTx{Code: 1, Log: err.Error()}
}

// TODO: CheckTx must reject transactions with a nonce <= currently mined
// account nonce. Also, with re-check happening (CheckTxType_Recheck) we
// must evict transactions that become invalid.
//
// Pathological cases:
// a. accept any txns to mempool -- that's a trivial mempool flooding attack.
// b. mine them (failed) -- that's a trivial block space attack.
// c. txns paying no fee -- flood, so a. and b. are crucial with no gas
if incoming.Type == abciTypes.CheckTxType_New {

// It is normally permissible to accept a transaction with the same nonce
// as a tx already in mempool (but not in a block), however without gas
// we would not want to allow that since there is no criteria
// for selecting the one to mine (normally higher fee).
if tx.Body.Nonce != uint64(acct.nonce)+1 {
logger.Error("nonce mismatch", zap.Uint64("txNonce", tx.Body.Nonce), zap.Int64("accountNonce", acct.nonce))
return abciTypes.ResponseCheckTx{Code: 1, Log: "nonce mismatch"}
}

a.mempool.nonceTracker[string(tx.Sender)] = tx.Body.Nonce
} else {
// Ensure nonce is higher than the last nonce for this account.
if tx.Body.Nonce <= uint64(acct.nonce) {
logger.Error("nonce mismatch", zap.Uint64("txNonce", tx.Body.Nonce), zap.Int64("accountNonce", acct.nonce))
return abciTypes.ResponseCheckTx{Code: 1, Log: "nonce mismatch"}
}
}

if !a.withoutGasCosts {
// Check if the sender has enough balance to pay for the tx fee
if tx.Body.Fee.Cmp(acct.balance) > 0 {
logger.Error("insufficient account balance", zap.String("balance", acct.balance.String()))
return abciTypes.ResponseCheckTx{Code: 1, Log: "insufficient account balance"}
}
}
// spend fee and nonce
a.updateAccount(tx.Sender, tx.Body.Fee)
return abciTypes.ResponseCheckTx{Code: 0}
}

Expand Down Expand Up @@ -522,6 +597,8 @@ func (a *AbciApp) Commit() abciTypes.ResponseCommit {
// Unlock all the DBs
}

a.mempool.reset()

return abciTypes.ResponseCommit{
Data: appHash,
}
Expand Down Expand Up @@ -793,6 +870,43 @@ func (a *AbciApp) PrepareProposal(p0 abciTypes.RequestPrepareProposal) abciTypes
}
}

func (a *AbciApp) validateProposalTransactions(ctx context.Context, Txns [][]byte) error {
grouped, err := groupTxsBySender(Txns)
if err != nil {
a.log.Error("failed to group transaction based on sender: ", zap.Error(err))
return err
}

for sender, txs := range grouped {
var expectedNonce uint64 = 1
acct, err := a.accounts.GetAccount(ctx, []byte(sender))
if err != nil {
return err
}

if acct != nil {
expectedNonce = uint64(acct.Nonce) + 1
}

for _, tx := range txs {
if tx.Body.Nonce != expectedNonce {
a.log.Info("Nonce mismatch",
zap.String("stage", "ABCI ProcessProposal"),
zap.Uint64("txNonce", tx.Body.Nonce),
zap.Uint64("expectedNonce", expectedNonce))
return fmt.Errorf("nonce mismatch, ExpectedNonce: %d TxNonce: %d", expectedNonce, tx.Body.Nonce)
}
expectedNonce++
}
}
return nil
}

// Reject the block if:
// 1. Any gap in the nonces
// 2. Any duplicate nonces
// 3. Any nonce is less than the last committed nonce for the account
// 4. transactions are not ordered by nonces
func (a *AbciApp) ProcessProposal(p0 abciTypes.RequestProcessProposal) abciTypes.ResponseProcessProposal {
a.log.Debug("",
zap.String("stage", "ABCI ProcessProposal"),
Expand All @@ -803,7 +917,12 @@ func (a *AbciApp) ProcessProposal(p0 abciTypes.RequestProcessProposal) abciTypes
// previous best confirmed or within this block. Our current transaction
// execution does not update an accounts nonce in state unless it is the
// next nonce. Delivering transactions to a block in that way cannot happen.
ctx := context.Background()
if err := a.validateProposalTransactions(ctx, p0.Txs); err != nil {
return abciTypes.ResponseProcessProposal{Status: abciTypes.ResponseProcessProposal_REJECT}
}

// TODO: Verify the Tx and Block sizes based on the genesis configuration
return abciTypes.ResponseProcessProposal{Status: abciTypes.ResponseProcessProposal_ACCEPT}
}

Expand Down
Loading

0 comments on commit 6c91804

Please sign in to comment.